package mylittleinvestigations.CaseInformation;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
import javax.imageio.ImageIO;
import mylittleinvestigations.Animation;
import mylittleinvestigations.Area;
import mylittleinvestigations.CharacterPosition;
import mylittleinvestigations.Collisions.CollidableObject;
import mylittleinvestigations.Collisions.HitBox;
import mylittleinvestigations.Condition;
import mylittleinvestigations.Confrontation;
import mylittleinvestigations.Conversation;
import mylittleinvestigations.Crowd;
import mylittleinvestigations.DialogCharacter;
import mylittleinvestigations.DialogCutscenes.DialogCutsceneAnimation;
import mylittleinvestigations.DialogCutscenes.DialogCutsceneAnimationElement;
import mylittleinvestigations.DialogCutscenes.DialogCutsceneAnimationFrame;
import mylittleinvestigations.Encounter;
import mylittleinvestigations.FieldCharacters.CharacterDirection;
import mylittleinvestigations.FieldCharacters.FieldCharacter;
import mylittleinvestigations.FieldCharacters.FieldCharacterDirection;
import mylittleinvestigations.FieldCharacters.FieldCharacterState;
import mylittleinvestigations.FieldCursorState;
import mylittleinvestigations.FieldCutscene;
import mylittleinvestigations.ForegroundElement;
import mylittleinvestigations.ForegroundElementAnimation;
import mylittleinvestigations.HeightMaps.ParabolicHeightMap;
import mylittleinvestigations.HiddenForegroundElement;
import mylittleinvestigations.Interrogation;
import mylittleinvestigations.Line;
import mylittleinvestigations.Location;
import mylittleinvestigations.Partner;
import mylittleinvestigations.Sprite;
import mylittleinvestigations.TransitionDirection;
import mylittleinvestigations.Utils;
import mylittleinvestigations.Vector2;
import mylittleinvestigations.Video;
import mylittleinvestigations.ZoomedView;
import org.dawnmew.jpurewave.SoundSystem;
import org.newdawn.slick.Color;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Polygon;

public class CaseParser
{
    private static ArrayList<TokenLine> tokenLineList;
    private static TokenLine curTokenLine;

    public static void ParseCase(String caseSource) throws CaseParseException, SlickException, IOException
    {
        Case.DiscardInstance();
        ParseSourceIntoTokens(caseSource);

        try
        {
            String token = null;

            while ((token = GetNextToken()) != null)
            {
                if (token.equals("BeginDialogCutscenes"))
                {
                    ParseDialogCutscenes();
                }
                else if (token.equals("BeginAudio"))
                {
                    ParseAudio();
                }
                else if (token.equals("BeginCharacters"))
                {
                    ParseCharacters();
                }
                else if (token.equals("BeginEvidenceList"))
                {
                    ParseEvidenceList();
                }
                else if (token.equals("BeginContent"))
                {
                    ParseContent();
                }
                else if (token.equals("BeginCaseMetadata"))
                {
                    ParseCaseMetadata();
                }
                else
                {
                    ThrowForUnknownToken(token);
                }
            }

            Case.getInstance().RunDependencyChecks();
        }
        catch (CaseParseException e)
        {
            throw new CaseParseException("Line " + curTokenLine.getLineNumber() + ": " + e.getMessage(), e);
        }
        
        if (Case.getInstance().getMetadata() == null)
        {
            throw new CaseParseException("Case must have associated metadata.");
        }
    }

    private static void ParseDialogCutscenes() throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean dialogCutscenesComplete = false;

        EndLine();

        while (!dialogCutscenesComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndDialogCutscenes found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginDialogCutscene"))
            {
                ParseDialogCutscene();
            }
            else if (token.equals("EndDialogCutscenes"))
            {
                dialogCutscenesComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseDialogCutscene() throws CaseParseException, SlickException, IOException
    {
        DialogCutsceneAnimation cutsceneAnimation = new DialogCutsceneAnimation();
        cutsceneAnimation.setId(GetNextToken());

        if (cutsceneAnimation.getId() == null)
        {
            throw new CaseParseException("Dialog cutscene must have an ID.");
        }

        String backgroundTextureFilePath = null;
        String backgroundTextureId = cutsceneAnimation.getId() + "Background";
        String token = null;
        boolean cutsceneComplete = false;

        EndLine();

        while (!cutsceneComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndDialogCutscene found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Background"))
            {
                backgroundTextureFilePath = GetRestOfLine();

                Case.getInstance().getSpriteManager().AddTexture(backgroundTextureId, backgroundTextureFilePath);
                cutsceneAnimation.setBackgroundTextureId(backgroundTextureId);
            }
            else if (token.equals("Element"))
            {
                String elementId = cutsceneAnimation.getId() + GetNextToken();

                int originalXPosition = Integer.parseInt(GetNextToken());
                int originalYPosition = Integer.parseInt(GetNextToken());

                int originalATint = Integer.parseInt(GetNextToken());
                int originalRTint = Integer.parseInt(GetNextToken());
                int originalGTint = Integer.parseInt(GetNextToken());
                int originalBTint = Integer.parseInt(GetNextToken());

                String textureFileName = GetRestOfLine();

                Case.getInstance().getSpriteManager().AddTexture(elementId, textureFileName);
                DialogCutsceneAnimationElement element = new DialogCutsceneAnimationElement(elementId, originalXPosition, originalYPosition, originalATint, originalRTint, originalGTint, originalBTint);

                cutsceneAnimation.AddElement(elementId, element);
            }
            else if (token.equals("BeginFrame"))
            {
                ParseFrame(cutsceneAnimation);
            }
            else if (token.equals("EndDialogCutscene"))
            {
                Case.getInstance().getDialogCutsceneManager().AddAnimation(cutsceneAnimation.getId(), cutsceneAnimation);
                cutsceneComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseFrame(DialogCutsceneAnimation animation) throws CaseParseException, SlickException, IOException
    {
        int msFrameDuration = Integer.parseInt(GetNextToken());
        DialogCutsceneAnimationFrame frame = new DialogCutsceneAnimationFrame(msFrameDuration);

        String token = null;
        boolean frameComplete = false;

        EndLine();

        while (!frameComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndFrame found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("MoveElement"))
            {
                String movedElementId = animation.getId() + GetNextToken();
                int newXPosition = Integer.parseInt(GetNextToken());
                int newYPosition = Integer.parseInt(GetNextToken());

                frame.AddPositionChange(movedElementId, newXPosition, newYPosition);
                EndLine();
            }
            else if(token.equals("TintElement"))
            {
                String tintedElementId = animation.getId() + GetNextToken();
                int newATint = Integer.parseInt(GetNextToken());
                int newRTint = Integer.parseInt(GetNextToken());
                int newGTint = Integer.parseInt(GetNextToken());
                int newBTint = Integer.parseInt(GetNextToken());

                frame.AddTintChange(tintedElementId, newATint, newRTint, newGTint, newBTint);
                EndLine();
            }
            else if (token.equals("ChangeBackground"))
            {
                String backgroundFilePath = GetRestOfLine();
                String backgroundTextureId = animation.getId() + "NewBackground" + animation.getChangeBackgroundCount();
                
                animation.incrementChangeBackgroundCount();

                Case.getInstance().getSpriteManager().AddTexture(backgroundTextureId, backgroundFilePath);
                frame.AddBackgroundChange(backgroundTextureId);
            }
            else if(token.equals("EndFrame"))
            {
                animation.AddFrame(frame);
                frameComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseAudio() throws CaseParseException
    {
        String token = null;
        boolean audioComplete = false;

        EndLine();

        while (!audioComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndAudio found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Bgm"))
            {
                String bgmId = GetNextToken();
                String bgmFilePath = GetRestOfLine();

                SoundSystem.getInstance().loadMusic(bgmId, bgmFilePath,false);
            }
            else if (token.equals("Sfx"))
            {
                String sfxId = GetNextToken();
                String sfxFilePath = GetRestOfLine();

                SoundSystem.getInstance().loadSound(sfxId, sfxFilePath);
            }
            else if (token.equals("BeginHoofstepSounds"))
            {
                ParseHoofstepSounds();
            }
            else if (token.equals("EndAudio"))
            {
                audioComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseHoofstepSounds() throws CaseParseException
    {
        String token = null;
        boolean hoofstepSoundsComplete = false;

        EndLine();

        while (!hoofstepSoundsComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndHoofstepSounds found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginTexture"))
            {
                String textureName = GetRestOfLine();
                Case.getInstance().getAudioManager().BeginHoofstepTexture(textureName);
            }
            else if (token.equals("HoofstepSoundId"))
            {
                String sfxId = GetRestOfLine();
                Case.getInstance().getAudioManager().AddHoofstepSoundId(sfxId);
                
                Case.getInstance().getAudioManager().RequestSfx(sfxId);
            }
            else if (token.equals("EndTexture"))
            {
                EndLine();
                Case.getInstance().getAudioManager().EndHoofstepTexture();
            }
            else if (token.equals("EndHoofstepSounds"))
            {
                hoofstepSoundsComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseCharacters() throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean charactersComplete = false;

        EndLine();

        while (!charactersComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCharacters found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginCharacter"))
            {
                ParseCharacter();
            }
            else if (token.equals("EndCharacters"))
            {
                charactersComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseCharacter() throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean characterComplete = false;

        String characterId = GetNextToken();
        String characterName = GetRestOfLine();
        String defaultEmotion = null;
        ArrayList<String> emotionsSeen = new ArrayList<String>();
        String[] standingAnimationIds = null;
        String[] walkingAnimationIds = null;
        String[] runningAnimationIds = null;
        HitBox hitBox = null;
        int anchorPosition = 0;
        Rectangle clickRect = new Rectangle(0, 0, 0, 0);

        if (characterId == null)
        {
            throw new CaseParseException("Character must have an ID.");
        }
        else if (characterName == null)
        {
            throw new CaseParseException("Character '" + characterId + "' must have a name.");
        }

        DialogCharacter dialogCharacter = new DialogCharacter(characterId, characterName);
        Partner partner = new Partner(characterId, characterName);

        while (!characterComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCharacters found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("PartnerIcon"))
            {
                String partnerIconFilePath = GetRestOfLine();
                String partnerIconTextureId = characterId + "PartnerIcon";

                Case.getInstance().getSpriteManager().AddTexture(partnerIconTextureId, partnerIconFilePath);
                partner.setIconSpriteId(partnerIconTextureId);
            }
            else if (token.equals("PartnerProfileImage"))
            {
                String partnerProfileImageFilePath = GetRestOfLine();
                String partnerProfileImageTextureId = characterId + "PartnerProfileImage";

                Case.getInstance().getSpriteManager().AddTexture(partnerProfileImageTextureId, partnerProfileImageFilePath);
                partner.setProfileImageSpriteId(partnerProfileImageTextureId);
            }
            else if (token.equals("PassiveAbilityDescription"))
            {
                partner.setPassiveAbilityDescription(GetRestOfLine());
            }
            else if (token.equals("ActiveAbilityDescription"))
            {
                partner.setActiveAbilityDescription(GetRestOfLine());
            }
            else if (token.equals("FieldAbilityName"))
            {
                partner.setFieldAbilityName(GetRestOfLine());
            }
            else if (token.equals("ConversationAbilityName"))
            {
                partner.setConversationAbilityName(GetRestOfLine());
            }
            else if (token.equals("BeginPartnerConversations"))
            {
                ParsePartnerConversations(partner);
            }
            else if (token.equals("HealthBackgroundSprite"))
            {
                String healthBackgroundSpriteTextureId = characterId + "HealthBackgroundSprite";
                String healthBackgroundSpriteFilePath = GetRestOfLine();

                Case.getInstance().getSpriteManager().AddTexture(healthBackgroundSpriteTextureId, healthBackgroundSpriteFilePath);
                dialogCharacter.SetHealthBackgroundSpriteTextureId(healthBackgroundSpriteTextureId);
            }
            else if (token.equals("EntranceForegroundSprite"))
            {
                String entranceForegroundSpriteTextureId = characterId + "EntranceForegroundSprite";
                String entranceForegroundSpriteFilePath = GetRestOfLine();

                Case.getInstance().getSpriteManager().AddTexture(entranceForegroundSpriteTextureId, entranceForegroundSpriteFilePath);
                dialogCharacter.SetEntranceForegroundSpriteTextureId(entranceForegroundSpriteTextureId);
                
                // We also want a pure white version of the image for the purposes of the light effect,
                // so let's make that now.
                BufferedImage image = ImageIO.read(Paths.get("data", entranceForegroundSpriteFilePath).toFile());
                Graphics2D imageGraphics = image.createGraphics();
                imageGraphics.setComposite(AlphaComposite.SrcIn);
                imageGraphics.setColor(java.awt.Color.white);
                imageGraphics.fillRect(0, 0, image.getWidth(), image.getHeight());
                
                Path tempFilePath = Files.createTempFile(null, ".png");
                ImageIO.write(image, "png", tempFilePath.toFile());
                Case.getInstance().getSpriteManager().AddTexture(entranceForegroundSpriteTextureId + "White", tempFilePath.toAbsolutePath().toString());
            }
            else if (token.equals("BackgroundColor"))
            {
                Color backgroundColor = new Color((float)Integer.parseInt(GetNextToken()) / 255.0f, (float)Integer.parseInt(GetNextToken()) / 255.0f, (float)Integer.parseInt(GetNextToken()) / 255.0f);
                dialogCharacter.SetBackgroundColor(backgroundColor);
                
                EndLine();
            }
            else if (token.equals("DefaultEmotion"))
            {
                defaultEmotion = GetNextToken();
                
                EndLine();
            }
            else if (token.equals("BeginEmotion"))
            {
                emotionsSeen.add(ParseEmotion(characterId, dialogCharacter));
            }
            else if (token.equals("BeginZoom"))
            {
                emotionsSeen.add(ParseEmotionCore(characterId, dialogCharacter, "Zoom", "EndZoom"));
            }
            else if (token.equals("BeginOneTimeEmotion"))
            {
                emotionsSeen.add(ParseOneTimeEmotion(characterId, dialogCharacter));
            }
            else if (token.equals("BeginBreakdown"))
            {
                String videoId = characterId + "Breakdown";
                ParseVideo(videoId, "EndBreakdown");
                dialogCharacter.SetBreakdownVideoId(videoId);
                
                Case.getInstance().getAnimationManager().GetVideoFromId(videoId).setShouldLoop(false);
            }
            else if (token.equals("BeginFieldCursorDefinitions"))
            {
                ParseFieldCursorDefinitions(partner, characterId + "FieldCursorAnimation", "EndFieldCursorDefinitions");
            }
            else if (token.equals("BeginStandingAnimation"))
            {
                standingAnimationIds = ParseFieldCharacterAnimations(characterId + "StandingAnimation", "EndStandingAnimation");
            }
            else if (token.equals("BeginWalkingAnimation"))
            {
                walkingAnimationIds = ParseFieldCharacterAnimations(characterId + "WalkingAnimation", "EndWalkingAnimation");
            }
            else if (token.equals("BeginRunningAnimation"))
            {
                runningAnimationIds = ParseFieldCharacterAnimations(characterId + "RunningAnimation", "EndRunningAnimation");
            }
            else if (token.equals("BeginHitBox"))
            {
                hitBox = ParseHitBox();
            }
            else if (token.equals("AnchorPosition"))
            {
                anchorPosition = Integer.parseInt(GetNextToken());
                EndLine();
            }
            else if (token.equals("ClickRect"))
            {
                clickRect = ParseRectangle();
                EndLine();
            }
            else if (token.equals("PresentCorrectEvidence"))
            {
                String soundFilePath = GetNextToken();
                String promptedSoundFilePath = GetNextToken();
                
                String spriteFilePath = GetRestOfLine();
                String spriteId = characterId + "PresentCorrectEvidence";
                
                Case.getInstance().getAudioManager().AddDialogFilePath(soundFilePath);
                Case.getInstance().getAudioManager().AddDialogFilePath(promptedSoundFilePath);
                Case.getInstance().getSpriteManager().AddTexture(spriteId, spriteFilePath);
                
                dialogCharacter.SetPresentCorrectEvidenceSoundId(soundFilePath);
                dialogCharacter.SetPresentCorrectEvidencePromptedSoundId(promptedSoundFilePath);
                dialogCharacter.SetPresentCorrectEvidenceSpriteId(spriteId);
            }
            else if (token.equals("PresentIncorrectEvidence"))
            {
                String soundFilePath = GetNextToken();
                
                String spriteFilePath = GetRestOfLine();
                String spriteId = characterId + "PresentIncorrectEvidence";
                
                Case.getInstance().getAudioManager().AddDialogFilePath(soundFilePath);
                Case.getInstance().getSpriteManager().AddTexture(spriteId, spriteFilePath);
                
                dialogCharacter.SetPresentIncorrectEvidenceSoundId(soundFilePath);
                dialogCharacter.SetPresentIncorrectEvidenceSpriteId(spriteId);
            }
            else if (token.equals("PressStatement"))
            {
                String soundFilePath = GetNextToken();
                
                String spriteFilePath = GetRestOfLine();
                String spriteId = characterId + "PressStatement";
                
                Case.getInstance().getAudioManager().AddDialogFilePath(soundFilePath);
                Case.getInstance().getSpriteManager().AddTexture(spriteId, spriteFilePath);
                
                dialogCharacter.SetPressStatementSoundId(soundFilePath);
                dialogCharacter.SetPressStatementSpriteId(spriteId);
            }
            else if (token.equals("Interjection"))
            {
                String soundFilePath = GetNextToken();
                
                String spriteFilePath = GetRestOfLine();
                String spriteId = characterId + "Interjection";
                
                Case.getInstance().getAudioManager().AddDialogFilePath(soundFilePath);
                Case.getInstance().getSpriteManager().AddTexture(spriteId, spriteFilePath);
                
                dialogCharacter.SetInterjectionSoundId(soundFilePath);
                dialogCharacter.SetInterjectionSpriteId(spriteId);
            }
            else if (token.equals("EndCharacter"))
            {
                if (defaultEmotion == null)
                {
                    throw new CaseParseException("Character must have a default emotion.");
                }
                else if (!emotionsSeen.contains(defaultEmotion))
                {
                    throw new CaseParseException("Default emotion '" + defaultEmotion + "' is not defined on character '" + characterId + "'.");
                }
                
                dialogCharacter.setDefaultEmotionId(defaultEmotion);
                
                Case.getInstance().getDialogCharacterManager().AddCharacter(characterId, dialogCharacter);
                Case.getInstance().getPartnerManager().AddPartner(characterId, partner);

                if (standingAnimationIds == null || walkingAnimationIds == null)
                {
                    throw new CaseParseException("Character must have both a standing animation and a walking animation.");
                }

                FieldCharacter fieldCharacter = new FieldCharacter(characterId, characterName, standingAnimationIds, walkingAnimationIds, runningAnimationIds != null ? runningAnimationIds : walkingAnimationIds);
                fieldCharacter.setHitBox(hitBox);
                fieldCharacter.setAnchorPosition(anchorPosition);
                fieldCharacter.setClickRect(clickRect);
                Case.getInstance().getFieldCharacterManager().AddCharacter(characterId, fieldCharacter);

                characterComplete = true;
                EndLine();
            }
            else   
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParsePartnerConversations(Partner partner) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean partnerConversationsComplete = false;
        
        EndLine();

        while (!partnerConversationsComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndPartnerConversations found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginPartnerConversation"))
            {
                ParsePartnerConversation(partner);
            }
            else if (token.equals("EndPartnerConversations"))
            {
                partnerConversationsComplete = true;
                EndLine();
            }
            else   
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParsePartnerConversation(Partner partner) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean partnerConversationComplete = false;
        
        Condition condition = null;
        Conversation conversation = null;
        
        EndLine();

        while (!partnerConversationComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndPartnerConversation found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginCondition"))
            {
                condition = ParseCondition();
            }
            else if (token.equals("BeginConversation"))
            {
                conversation = ParseConversation();
            }
            else if (token.equals("EndPartnerConversation"))
            {
                if (condition == null || conversation == null)   
                {
                    throw new CaseParseException("Partner conversation must have both a condition and a conversation.");
                }
                
                partner.AddConversation(condition, conversation);
                partnerConversationComplete = true;
                EndLine();
            }
            else   
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static String ParseEmotion(String characterId, DialogCharacter character) throws CaseParseException, SlickException, IOException
    {
        String emotionId = GetNextToken();

        if (emotionId == null)
        {
            throw new CaseParseException("Emotion must have an ID.");
        }
        else if (emotionId.equals("Zoom"))
        {
            throw new CaseParseException("Emotion ID 'Zoom' is reserved for the zoom sprites.");
        }
        
        return ParseEmotionCore(characterId, character, emotionId, "EndEmotion");
    }

    private static String ParseEmotionCore(String characterId, DialogCharacter character, String emotionId, String endEmotionToken) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean emotionComplete = false;

        String baseFilePath = null;
        ArrayList<String> eyeFrameFilePathList = null;
        ArrayList<String> mouthFrameFilePathList = null;

        String rootTextureId = characterId + emotionId;

        EndLine();

        while (!emotionComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No " + endEmotionToken + " found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Base"))
            {
                baseFilePath = GetRestOfLine();
            }
            else if (token.equals("BeginEyeFrames"))
            {
                eyeFrameFilePathList = ParseEmotionFrames("EndEyeFrames");
            }
            else if (token.equals("BeginMouthFrames"))
            {
                mouthFrameFilePathList = ParseEmotionFrames("EndMouthFrames");
            }
            else if (token.equals("BeginForegroundLayers"))
            {
                ParseForegroundLayers(character, characterId, emotionId);
            }
            else if (token.equals(endEmotionToken))
            {
                if (baseFilePath == null)
                {
                    throw new CaseParseException("Emotion '" + emotionId + "' must have file paths for at least the base sprite.");
                }
                
                Case.getInstance().getSpriteManager().AddTexture(rootTextureId + "Base", baseFilePath);
                character.AddBaseSpriteForEmotion(emotionId, rootTextureId + "Base");
                
                if (eyeFrameFilePathList != null)
                {
                    for (int i = 0; i < eyeFrameFilePathList.size(); i++)
                    {
                        Case.getInstance().getSpriteManager().AddTexture(rootTextureId + "Eye" + i, eyeFrameFilePathList.get(i));
                        character.AddEyeSpriteForEmotion(emotionId, rootTextureId + "Eye" + i);
                    }
                }
                
                if (mouthFrameFilePathList != null)
                {
                    for (int i = 0; i < mouthFrameFilePathList.size(); i++)
                    {
                        Case.getInstance().getSpriteManager().AddTexture(rootTextureId + "Mouth" + i, mouthFrameFilePathList.get(i));
                        character.AddMouthSpriteForEmotion(emotionId, rootTextureId + "Mouth" + i);
                    }
                }
            
                emotionComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
        
        return emotionId;
    }

    private static String ParseOneTimeEmotion(String characterId, DialogCharacter character) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean emotionComplete = false;
        
        String emotionId = GetNextToken();
        
        String transitionToEmotion = null;
        int leadInTime = 0;
        String videoId = characterId + emotionId;
        boolean videoFound = false;

        EndLine();

        while (!emotionComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCharacters found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("TransitionTo"))
            {
                transitionToEmotion = GetNextToken();
                EndLine();
            }
            else if (token.equals("LeadInTime"))
            {
                leadInTime = Integer.parseInt(GetNextToken());
                EndLine();
            }
            else if (token.equals("BeginAnimation"))
            {
                ParseVideo(videoId, "EndAnimation");
                videoFound = true;
                
                Case.getInstance().getAnimationManager().GetVideoFromId(videoId).setShouldLoop(false);
            }
            else if (token.equals("EndOneTimeEmotion"))
            {
                if (!videoFound)
                {
                    throw new CaseParseException("One-time emotion must have an associated animation.");
                }
                
                character.AddOneTimeEmotion(emotionId, transitionToEmotion, leadInTime, videoId);
            
                emotionComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
        
        return emotionId;
    }
    
    private static ArrayList<String> ParseEmotionFrames(String endToken) throws CaseParseException
    {
        String token = null;
        boolean emotionFramesComplete = false;

        ArrayList<String> frameFilePathList = new ArrayList<String>();

        EndLine();

        while (!emotionFramesComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCharacters found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Frame"))
            {
                frameFilePathList.add(GetRestOfLine());
            }
            else if (token.equals(endToken))
            {
                emotionFramesComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
        
        return frameFilePathList;
    }
    
    private static void ParseForegroundLayers(DialogCharacter character, String characterId, String emotionId) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean foregroundLayersComplete = false;
        String animationIdBase = characterId + emotionId + "ForegroundLayer";
        int layerCount = 0;
        
        EndLine();

        while (!foregroundLayersComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndForegroundLayers found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginForegroundLayer"))
            {
                String animationId = animationIdBase + (++layerCount);
                ParseAnimation(animationId, "EndForegroundLayer");
                character.AddForegroundLayerForEmotion(emotionId, animationId);
                
                Animation animation = Case.getInstance().getAnimationManager().GetAnimationFromId(animationId);
                animation.setGroupId("DialogCharacter" + characterId + emotionId);
            }
            else if (token.equals("EndForegroundLayers"))
            {
                foregroundLayersComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }
    
    private static void ParseFieldCursorDefinitions(Partner partner, String animationBaseId, String endToken) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        Vector2 clickPointOffset = new Vector2(0, 0);
        boolean[] fieldCursorsFound = new boolean[FieldCursorState.values().length];
        boolean fieldCursorDefinitionsComplete = false;
        
        for (int i = 0; i < FieldCursorState.values().length; i++)
        {
            fieldCursorsFound[i] = false;
        }

        EndLine();

        while (!fieldCursorDefinitionsComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndFieldCursorAnimations found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("ClickPointOffset"))
            {
                clickPointOffset = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                EndLine();
            }
            else if (token.equals("TurnOnSound"))
            {
                String sfxId = GetNextToken();
                partner.setTurnOnSoundId(sfxId);
                EndLine();
                
                Case.getInstance().getAudioManager().RequestSfx(sfxId);
            }
            else if (token.equals("TurnOffSound"))
            {
                String sfxId = GetNextToken();
                partner.setTurnOffSoundId(sfxId);
                EndLine();
                
                Case.getInstance().getAudioManager().RequestSfx(sfxId);
            }
            else if (token.equals("BeginNone"))
            {
                fieldCursorsFound[FieldCursorState.None.ordinal()] = true;
                ParseFieldCursorDefinition(partner, animationBaseId, FieldCursorState.None, "EndNone");
            }
            else if (token.equals("BeginLow"))
            {
                fieldCursorsFound[FieldCursorState.Low.ordinal()] = true;
                ParseFieldCursorDefinition(partner, animationBaseId, FieldCursorState.Low, "EndLow");
            }
            else if (token.equals("BeginMid"))
            {
                fieldCursorsFound[FieldCursorState.Mid.ordinal()] = true;
                ParseFieldCursorDefinition(partner, animationBaseId, FieldCursorState.Mid, "EndMid");
            }
            else if (token.equals("BeginHigh"))
            {
                fieldCursorsFound[FieldCursorState.High.ordinal()] = true;
                ParseFieldCursorDefinition(partner, animationBaseId, FieldCursorState.High, "EndHigh");
            }
            else if (token.equals("BeginExtreme"))
            {
                fieldCursorsFound[FieldCursorState.Extreme.ordinal()] = true;
                ParseFieldCursorDefinition(partner, animationBaseId, FieldCursorState.Extreme, "EndExtreme");
            }
            else if (token.equals("BeginTransitionOverlay"))
            {
                fieldCursorsFound[FieldCursorState.Overlay.ordinal()] = true;
                ParseFieldCursorDefinition(partner, animationBaseId, FieldCursorState.Overlay, "EndTransitionOverlay");
            }
            else if (token.equals(endToken))
            {
                if (!fieldCursorsFound[FieldCursorState.None.ordinal()] ||
                        !fieldCursorsFound[FieldCursorState.Low.ordinal()] ||
                        !fieldCursorsFound[FieldCursorState.Mid.ordinal()] ||
                        !fieldCursorsFound[FieldCursorState.High.ordinal()] ||
                        !fieldCursorsFound[FieldCursorState.Extreme.ordinal()] ||
                        !fieldCursorsFound[FieldCursorState.Overlay.ordinal()])
                {
                    throw new CaseParseException("Cursor definitions must include definitions for all states: None, Low, Mid, High, Extreme, and Transition Overlay.");
                }
                
                partner.setClickPointOffset(clickPointOffset);
                
                fieldCursorDefinitionsComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }
    
    private static void ParseFieldCursorDefinition(Partner partner, String animationBaseId, FieldCursorState state, String endToken) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        String sfxId = null;
        String animationId = null;
        boolean fieldCursorDefinitionComplete = false;

        EndLine();

        while (!fieldCursorDefinitionComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndFieldCursorDefinition found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Sound"))
            {
                sfxId = GetNextToken();
                Case.getInstance().getAudioManager().RequestSfx(sfxId);
                EndLine();
            }
            else if (token.equals("BeginAnimation"))
            {
                animationId = animationBaseId + state.name();
                ParseAnimation(animationId, "EndAnimation");
            }
            else if (token.equals(endToken))
            {
                if (animationId == null)
                {
                    throw new CaseParseException("Cursor definition requires an animation definition.");
                }
                
                partner.AddFieldCursorDefinition(state, animationId, sfxId);
                
                fieldCursorDefinitionComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }
    
    private static String[] ParseFieldCharacterAnimations(String animationBaseId, String endToken) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean fieldCharacterAnimationComplete = false;

        String[] fieldCharacterAnimationIds = new String[FieldCharacterDirection.values().length];

        EndLine();

        while (!fieldCharacterAnimationComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndFieldCharacterAnimations found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginUp"))
            {
                fieldCharacterAnimationIds[FieldCharacterDirection.Up.ordinal()] = animationBaseId + "Up";
                ParseAnimation(animationBaseId + "Up", "EndUp");
            }
            else if (token.equals("BeginDiagonalUp"))
            {
                fieldCharacterAnimationIds[FieldCharacterDirection.DiagonalUp.ordinal()] = animationBaseId + "DiagonalUp";
                ParseAnimation(animationBaseId + "DiagonalUp", "EndDiagonalUp");
            }
            else if (token.equals("BeginSide"))
            {
                fieldCharacterAnimationIds[FieldCharacterDirection.Side.ordinal()] = animationBaseId + "Side";
                ParseAnimation(animationBaseId + "Side", "EndSide");
            }
            else if (token.equals("BeginDiagonalDown"))
            {
                fieldCharacterAnimationIds[FieldCharacterDirection.DiagonalDown.ordinal()] = animationBaseId + "DiagonalDown";
                ParseAnimation(animationBaseId + "DiagonalDown", "EndDiagonalDown");
            }
            else if (token.equals("BeginDown"))
            {
                fieldCharacterAnimationIds[FieldCharacterDirection.Down.ordinal()] = animationBaseId + "Down";
                ParseAnimation(animationBaseId + "Down", "EndDown");
            }
            else if (token.equals(endToken))
            {
                if (fieldCharacterAnimationIds[FieldCharacterDirection.Up.ordinal()] == null ||
                        fieldCharacterAnimationIds[FieldCharacterDirection.DiagonalUp.ordinal()] == null ||
                        fieldCharacterAnimationIds[FieldCharacterDirection.Side.ordinal()] == null ||
                        fieldCharacterAnimationIds[FieldCharacterDirection.DiagonalDown.ordinal()] == null ||
                        fieldCharacterAnimationIds[FieldCharacterDirection.Down.ordinal()] == null)
                {
                    throw new CaseParseException("Character definition must include animations for all directions: Up, Diagonal Up, Side, Diagonal Down, and Down.");
                }
                
                fieldCharacterAnimationComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
        
        return fieldCharacterAnimationIds;
    }

    private static void ParseEvidenceList() throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean evidenceListComplete = false;

        EndLine();

        while (!evidenceListComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndEvidenceList found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginEvidence"))
            {
                ParseEvidence();
            }
            else if (token.equals("BeginEvidenceCombination"))
            {
                String evidenceId1 = GetNextToken();
                String evidenceId2 = GetNextToken();
                Conversation evidenceCombinationConversation = ParseConversation("EndEvidenceCombination");
            
                Case.getInstance().getEvidenceManager().AddEvidenceCombination(evidenceId1, evidenceId2, evidenceCombinationConversation);
            }
            else if (token.equals("BeginWrongEvidenceCombination"))
            {
                Case.getInstance().getEvidenceManager().setWrongCombinationConversation(ParseConversation("EndWrongEvidenceCombination"));
            }
            else if (token.equals("EndEvidenceList"))
            {
                evidenceListComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseEvidence() throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean evidenceComplete = false;

        String evidenceId = GetNextToken();
        String evidenceName = GetRestOfLine();
        String evidenceSmallFilePath = null;
        String evidenceLargeFilePath = null;
        String evidenceDescription = null;
        boolean enabledAtStart = false;
        boolean isProfile = false;

        String evidenceSmallTextureId = evidenceId + "Small";
        String evidenceLargeTextureId = evidenceId + "Large";

        if (evidenceId == null)
        {
            throw new CaseParseException("Evidence must have an ID.");
        }

        while (!evidenceComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCharacters found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("EnableAtStart"))
            {
                enabledAtStart = true;
                EndLine();
            }
            else if (token.equals("IsProfile"))
            {
                isProfile = true;
                EndLine();
            }
            else if (token.equals("Small"))
            {
                evidenceSmallFilePath = GetRestOfLine();
            }
            else if (token.equals("Large"))
            {
                evidenceLargeFilePath = GetRestOfLine();
            }
            else if (token.equals("Description"))
            {
                evidenceDescription = GetRestOfLine().replace("\\n", "\n");
            }
            else if (token.equals("EndEvidence"))
            {
                if (evidenceSmallFilePath == null || evidenceLargeFilePath == null || evidenceDescription == null)
                {
                    throw new CaseParseException("Evidence '" + evidenceId + "' must have file paths for the small and large display pictures and a description.");
                }
            
                Case.getInstance().getSpriteManager().AddTexture(evidenceSmallTextureId, evidenceSmallFilePath);
                Case.getInstance().getSpriteManager().AddTexture(evidenceLargeTextureId, evidenceLargeFilePath);
            
                Case.getInstance().getEvidenceManager().Add(evidenceId, evidenceSmallTextureId, evidenceLargeTextureId, evidenceName, evidenceDescription, isProfile);
            
                if (enabledAtStart)
                {
                    Case.getInstance().getEvidenceManager().EnableEvidenceWithId(evidenceId);
                }
            
                evidenceComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseContent() throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean contentComplete = false;

        EndLine();

        while (!contentComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndContent found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("PlayerCharacter"))
            {
                String characterId = GetNextToken();
            
                Case.getInstance().getFieldCharacterManager().SetPlayerCharacterId(characterId);
                EndLine();
            }
            else if (token.equals("InitialStartLocation"))
            {
                Case.getInstance().getContent().setInitialAreaId(GetNextToken());
                Case.getInstance().getContent().setInitialLocationId(Case.getInstance().getContent().getInitialAreaId() + GetNextToken());
                EndLine();
            }
            else if (token.equals("BeginConversationSnippets"))
            {
                ParseConversationSnippets();
            }
            else if (token.equals("BeginArea"))
            {
                ParseArea();
            }
            else if (token.equals("BeginEncounter"))
            {
                ParseEncounter();
            }
            else if (token.equals("EndContent"))
            {
                contentComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseConversationSnippets() throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean conversationSnippetsComplete = false;

        EndLine();

        while (!conversationSnippetsComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndArea found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginConversationSnippet"))
            {
                String id = GetNextToken();
                Conversation conversation = ParseConversation("EndConversationSnippet");
                
                Case.getInstance().getContent().AddConversationSnippet(id, conversation);
            }
            else if (token.equals("EndConversationSnippets"))
            {
                conversationSnippetsComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseArea() throws CaseParseException, SlickException, IOException
    {
        Area area = new Area();
        String token = null;
        boolean areaComplete = false;

        area.setId(GetNextToken());

        if (area.getId() == null)
        {
            throw new CaseParseException("Area must have an ID.");
        }

        area.setName(GetRestOfLine());

        while (!areaComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndArea found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("StartLocation"))
            {
                area.setStartLocationId(area.getId() + GetNextToken());
                EndLine();
            }
            else if (token.equals("BeginLocation"))
            {
                ParseLocation(area);
            }
            else if (token.equals("EndArea"))
            {
                Case.getInstance().getContent().AddArea(area.getId(), area);
                areaComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseLocation(Area area) throws CaseParseException, SlickException, IOException
    {
        Location location = new Location();
        String token = null;
        boolean locationComplete = false;
        boolean seenStartPosition = false;
        NextNumberParameter param = new NextNumberParameter();

        location.setId(GetNextToken());

        if (location.getId() == null)
        {
            throw new CaseParseException("Location must have an ID.");
        }

        location.setId(area.getId() + location.getId());

        String backgroundTextureId = area.getId() + location.getId() + "Background";

        EndLine();

        while (!locationComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndLocation found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Background"))
            {
                String backgroundTextureFilePath = GetRestOfLine();
            
                Case.getInstance().getSpriteManager().AddTexture(backgroundTextureId, backgroundTextureFilePath);
                location.setBackgroundTextureId(backgroundTextureId);
            }
            else if (token.equals("LocationBgm"))
            {
                String bgmId = GetRestOfLine();
                location.setBgm(bgmId);
                Case.getInstance().getAudioManager().RequestBgm(bgmId);
            }
            else if (token.equals("LocationAmbiance"))
            {
                String sfxId = GetRestOfLine();
                location.setAmbianceSfxId(sfxId);
                Case.getInstance().getAudioManager().RequestSfx(sfxId);
            }
            else if (token.equals("StartPosition"))
            {
                CharacterDirection startDirection = ParseCharacterDirection();
                FieldCharacterDirection startFieldCharacterDirection = ParseFieldCharacterDirection();
                int startXPosition = Integer.parseInt(GetNextToken());
                int startYPosition = Integer.parseInt(GetNextToken());
                String previousLocationId = GetNextToken();
            
                if (previousLocationId != null)
                {
                    previousLocationId = area.getId() + previousLocationId;
                }
            
                location.AddStartPosition(previousLocationId, new Vector2(startXPosition, startYPosition), startDirection, startFieldCharacterDirection);
                seenStartPosition = true;
                EndLine();
            }
            else if (token.equals("PartnerStartPosition"))
            {
                CharacterDirection startDirection = ParseCharacterDirection();
                FieldCharacterDirection startFieldCharacterDirection = ParseFieldCharacterDirection();
                int startXPosition = Integer.parseInt(GetNextToken());
                int startYPosition = Integer.parseInt(GetNextToken());
                String previousLocationId = GetNextToken();
            
                if (previousLocationId != null)
                {
                    previousLocationId = area.getId() + previousLocationId;
                }
            
                location.AddPartnerStartPosition(previousLocationId, new Vector2(startXPosition, startYPosition), startDirection, startFieldCharacterDirection);
                EndLine();
            }
            else if (token.equals("BeginCharacter"))
            {
                ParseCharacter(location);
            }
            else if (token.equals("BeginCrowd"))
            {
                ParseCrowd(location);
            }
            else if (token.equals("LoopingSound"))
            {
                String soundId = GetNextToken();
                Vector2 origin = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                
                location.AddLoopingSound(soundId, origin);
                
                EndLine();
                
                Case.getInstance().getAudioManager().RequestSfx(soundId);
            }
            else if (token.equals("BeginHitBox"))
            {
                location.setAreaHitBox(ParseHitBox());
            }
            else if (token.equals("BeginTransitions"))
            {
                ParseTransitions(area, location);
            }
            else if (token.equals("BeginHeightMaps"))
            {
                ParseHeightMaps(location);
            }
            else if (token.equals("BeginForegroundElementList"))
            {
                ParseForegroundElementList(location, param);
            }
            else if (token.equals("BeginZoomedViewList"))
            {
                ParseZoomedViewList(location, param);
            }
            else if (token.equals("BeginCutscenes"))
            {
                ParseCutscenes(location);
            }
            else if (token.equals("EndLocation"))
            {
                if (location.getBackgroundTextureId() == null)
                {
                    throw new CaseParseException("Location '" + location.getId() + "' must have an associated background image.");
                }
                else if (!seenStartPosition)
                {
                    throw new CaseParseException("Location '" + location.getId() + "' must have a start position.");
                }
            
                Sprite backgroundSprite = Case.getInstance().getSpriteManager().GetSpriteFromId(location.getBackgroundTextureId());
            
                // Add on hitboxes to make it so the player can't walk off the edge of the screen.
                /*location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(-50, 0), new Vector2(50, backgroundSprite.getHeight())));
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(backgroundSprite.getWidth(), 0), new Vector2(50, backgroundSprite.getHeight())));
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(0, -50), new Vector2(backgroundSprite.getWidth(), 50)));
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(0, backgroundSprite.getHeight()), new Vector2(backgroundSprite.getWidth(), 50)));
                
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(-50, -50), new Vector2(50, 50)));
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(backgroundSprite.getWidth(), -50), new Vector2(50, 50)));
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(-50, backgroundSprite.getHeight()), new Vector2(50, 50)));
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(backgroundSprite.getWidth(), backgroundSprite.getHeight()), new Vector2(50, 50)));*/
                //location.getAreaHitBox().AddAreaBounds(new Rectangle(0, 0, (float)backgroundSprite.getWidth(), (float)backgroundSprite.getHeight()));
                
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(-backgroundSprite.getWidth(), -backgroundSprite.getHeight()), new Vector2(3 * backgroundSprite.getWidth(), backgroundSprite.getHeight())));
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(-backgroundSprite.getWidth(), -backgroundSprite.getHeight()), new Vector2(backgroundSprite.getWidth(), 3 * backgroundSprite.getHeight())));
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(-backgroundSprite.getWidth(), backgroundSprite.getHeight()), new Vector2(3 * backgroundSprite.getWidth(), backgroundSprite.getHeight())));
                location.getAreaHitBox().AddCollidableObject(CollidableObject.CreateRectangle(new Vector2(backgroundSprite.getWidth(), -backgroundSprite.getHeight()), new Vector2(backgroundSprite.getWidth(), 3 * backgroundSprite.getHeight())));
            
                Case.getInstance().getContent().AddLocation(location.getId(), location);
                locationComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseCharacter(Location location) throws CaseParseException
    {
        String token = null;
        String characterId = null;
        CharacterDirection direction = CharacterDirection.Left;
        FieldCharacterDirection spriteDirection = FieldCharacterDirection.Side;
        Vector2 characterPosition = new Vector2(0, 0);
        String clickId = null;
        Vector2 interactionLocation = null;
        boolean interactFromAnywhere = false;
        Condition condition = null;
        
        boolean characterComplete = false;

        EndLine();

        while (!characterComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCharacter found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Character"))
            {
                characterId = GetNextToken();
                direction = ParseCharacterDirection();
                spriteDirection = ParseFieldCharacterDirection();
                characterPosition = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                clickId = GetNextToken();
            
                EndLine();
            }
            else if (token.equals("InteractionLocation"))
            {
                interactionLocation = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
            
                EndLine();
            }
            else if (token.equals("InteractFromAnywhere"))
            {
                interactFromAnywhere = true;
                
                EndLine();
            }
            else if (token.equals("BeginCondition"))
            {
                condition = ParseCondition();
            }
            else if (token.equals("EndCharacter"))
            {
                if (characterId == null)
                {
                    throw new CaseParseException("Cannot have BeginCharacter without a Character definition.");
                }
                
                Case.getInstance().AddLocationCharacterDependencyCheck(characterId, direction, spriteDirection, characterPosition, clickId, interactionLocation, interactFromAnywhere, condition, location);
                
                characterComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseCrowd(Location location) throws CaseParseException, IOException
    {
        String token = null;
        Vector2 position = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
        int frameDuration = Integer.parseInt(GetNextToken());
        String name = GetRestOfLine();
        Crowd crowd = new Crowd(position, name, frameDuration);
        
        boolean crowdComplete = false;

        while (!crowdComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCrowd found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginHitBox"))
            {
                crowd.setHitBox(ParseHitBox());
            }
            else if (token.equals("ClickPolygon"))
            {
                crowd.setClickPolygon(ParsePolygon());
            }
            else if (token.equals("AnchorPosition"))
            {
                int anchorPosition = Integer.parseInt(GetNextToken());
                EndLine();
                
                crowd.setAnchorPosition(anchorPosition);
            }
            else if (token.equals("InteractionLocation"))
            {
                crowd.setInteractionLocation(new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken())));
                EndLine();
            }
            else if (token.equals("InteractFromAnywhere"))
            {
                crowd.setInteractFromAnywhere(true);
                EndLine();
            }
            else if (token.equals("BeginCharacter"))
            {
                Vector2 characterPosition = new Vector2(Float.parseFloat(GetNextToken()) - position.getX(), Float.parseFloat(GetNextToken()) - position.getY());
                EndLine();
                
                Crowd.Character crowdCharacter = crowd.AddCharacter(characterPosition);
                ParseCrowdCharacter(crowdCharacter);
            }
            else if (token.equals("BeginConversation"))
            {
                crowd.setConversation(ParseConversation());
            }
            else if (token.equals("EndCrowd"))
            {
                crowd.CreateVideo();
                location.AddCrowd(crowd);
                crowdComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseCrowdCharacter(Crowd.Character crowdCharacter) throws CaseParseException
    {
        String token = null;
        
        boolean crowdCharacterComplete = false;

        while (!crowdCharacterComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCharacter found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Frame"))
            {
                crowdCharacter.AddFrameFilePath(GetRestOfLine());
            }
            else if (token.equals("EndCharacter"))
            {
                crowdCharacterComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseTransitions(Area area, Location location) throws CaseParseException
    {
        String token = null;
        boolean transitionsComplete = false;

        EndLine();

        while (!transitionsComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndTransitions found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginTransition"))
            {
                ParseTransition(area, location);
            }
            else if (token.equals("EndTransitions"))
            {
                transitionsComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseTransition(Area area, Location location) throws CaseParseException
    {
        String token = null;
        String targetLocationId = area.getId() + GetNextToken();
        TransitionDirection transitionDirection = ParseTransitionDirection();
        String targetLocationName = GetRestOfLine();
        boolean transitionComplete = false;

        HitBox hitBox = new HitBox();
        Vector2 interactionLocation = new Vector2(-1, -1);
        boolean interactFromAnywhere = false;
        String transitionStartSfxId = null;
        String transitionEndSfxId = null;
        boolean hideWhenLocked = false;
        Condition condition = null;
        Conversation conversation = null;

        while (!transitionComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndTransition found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Triangle"))
            {
                Vector2 vertex1 = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
                Vector2 vertex2 = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
                Vector2 vertex3 = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
            
                hitBox.AddCollidableObject(CollidableObject.CreateTriangle(vertex1, vertex2, vertex3));
                EndLine();
            }
            else if (token.equals("Rectangle"))
            {
                Vector2 position = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
                Vector2 size = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
            
                hitBox.AddCollidableObject(CollidableObject.CreateRectangle(position, size));
                EndLine();
            }
            else if (token.equals("Polygon"))
            {
                String[] vertexPositionParameters = GetRestOfLine().split(" ");
                
                if (vertexPositionParameters.length % 2 == 1)
                {
                    throw new CaseParseException("Number of position parameters for Polygon must be even.");
                }
                
                ArrayList<Vector2> vertices = new ArrayList<Vector2>();
                
                for (int i = 0; i < vertexPositionParameters.length / 2; i++)
                {
                    vertices.add(new Vector2(Double.parseDouble(vertexPositionParameters[2 * i]), Double.parseDouble(vertexPositionParameters[2 * i + 1])));
                }
            
                hitBox.AddCollidableObject(CollidableObject.CreatePolygon(vertices));
            }
            else if (token.equals("InteractionLocation"))
            {
                interactionLocation = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
                EndLine();
            }
            else if (token.equals("InteractFromAnywhere"))
            {
                interactFromAnywhere = true;
                EndLine();
            }
            else if (token.equals("TransitionStartSound"))
            {
                transitionStartSfxId = GetNextToken();
                Case.getInstance().getAudioManager().RequestSfx(transitionStartSfxId);
                EndLine();
            }
            else if (token.equals("TransitionEndSound"))
            {
                transitionEndSfxId = GetNextToken();
                Case.getInstance().getAudioManager().RequestSfx(transitionEndSfxId);
                EndLine();
            }
            else if (token.equals("HideWhenLocked"))
            {
                hideWhenLocked = true;
                EndLine();
            }
            else if (token.equals("BeginCondition"))
            {
                condition = ParseCondition();
            }
            else if (token.equals("BeginConversation"))
            {
                conversation = ParseConversation();
            }
            else if (token.equals("EndTransition"))
            {
                location.AddTransition(targetLocationId, targetLocationName, hitBox, transitionDirection, interactionLocation, interactFromAnywhere, transitionStartSfxId, transitionEndSfxId, hideWhenLocked, condition, conversation);
                transitionComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseHeightMaps(Location location) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean heightMapsComplete = false;

        EndLine();

        while (!heightMapsComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndHeightMaps found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginParabolicHeightMap"))
            {
                location.AddHeightMap(ParseParabolicHeightMap());
            }
            else if (token.equals("EndHeightMaps"))
            {
                heightMapsComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static ParabolicHeightMap ParseParabolicHeightMap() throws CaseParseException, SlickException, IOException
    {
        String token = null;
        ParabolicHeightMap parabolicHeightMap = new ParabolicHeightMap();
        boolean parabolicHeightMapComplete = false;

        EndLine();

        while (!parabolicHeightMapComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndParabolicHeightMap found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("DirectionVector"))
            {
                parabolicHeightMap.setDirectionVector(new Vector2(Double.parseDouble(GetNextToken()), Double.parseDouble(GetNextToken())));
                EndLine();
            }
            else if (token.equals("BeginBoundingPolygon"))
            {
                parabolicHeightMap.setBoundingPolygon(ParseBoundingPolygon());
            }
            else if (token.equals("HeightLine1"))
            {
                Vector2 point1 = new Vector2(Double.parseDouble(GetNextToken()), Double.parseDouble(GetNextToken()));
                Vector2 point2 = new Vector2(Double.parseDouble(GetNextToken()), Double.parseDouble(GetNextToken()));
                int heightAtLine = Integer.parseInt(GetNextToken());
                
                parabolicHeightMap.SetHeightLine1(point1, point2.subtract(point1), heightAtLine);
                
                EndLine();
            }
            else if (token.equals("HeightLine2"))
            {
                Vector2 point1 = new Vector2(Double.parseDouble(GetNextToken()), Double.parseDouble(GetNextToken()));
                Vector2 point2 = new Vector2(Double.parseDouble(GetNextToken()), Double.parseDouble(GetNextToken()));
                int heightAtLine = Integer.parseInt(GetNextToken());
                
                parabolicHeightMap.SetHeightLine2(point1, point2.subtract(point1), heightAtLine);
                
                EndLine();
            }
            else if (token.equals("HeightLine3"))
            {
                Vector2 point1 = new Vector2(Double.parseDouble(GetNextToken()), Double.parseDouble(GetNextToken()));
                Vector2 point2 = new Vector2(Double.parseDouble(GetNextToken()), Double.parseDouble(GetNextToken()));
                int heightAtLine = Integer.parseInt(GetNextToken());
                
                parabolicHeightMap.SetHeightLine3(point1, point2.subtract(point1), heightAtLine);
                
                EndLine();
            }
            else if (token.equals("EndParabolicHeightMap"))
            {
                parabolicHeightMapComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
        
        return parabolicHeightMap;
    }

    private static Polygon ParseBoundingPolygon() throws CaseParseException, SlickException, IOException
    {
        String token = null;
        Polygon boundingPolygon = new Polygon();
        boolean boundingPolygonComplete = false;

        EndLine();

        while (!boundingPolygonComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndForegroundElements found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Vertex"))
            {
                boundingPolygon.addPoint(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                EndLine();
            }
            else if (token.equals("EndBoundingPolygon"))
            {
                boundingPolygonComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
        
        return boundingPolygon;
    }

    private static void ParseForegroundElementList(Location location, NextNumberParameter param) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean foregroundElementsComplete = false;

        EndLine();

        while (!foregroundElementsComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndForegroundElements found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginForegroundElement"))
            {
                ParseForegroundElement(location, param);
            }
            else if (token.equals("BeginHiddenForegroundElement"))
            {
                ParseHiddenForegroundElement(location, param);
            }
            else if (token.equals("EndForegroundElementList"))
            {
                foregroundElementsComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseForegroundElement(Location location, NextNumberParameter param) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean foregroundElementComplete = false;

        String textureId = null;
        Vector2 position = new Vector2(0, 0);
        int anchorPosition = -1;
        String name = null;
        Line zOrderLine = null;
        HitBox hitBox = null;
        Polygon clickPolygon = new Polygon();
        Condition condition = null;
        Conversation conversation = null;
        String zoomedViewId = null;
        Vector2 interactionLocation = null;
        boolean interactFromAnywhere = false;
        
        ArrayList<ForegroundElementAnimation> foregroundElementAnimationList = new ArrayList<ForegroundElementAnimation>();

        EndLine();

        while (!foregroundElementComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndForegroundElement found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Visual"))
            {
                position = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                String textureFileName = GetRestOfLine();
                textureId = location.getId() + "ForegroundElement" + param.NextForegroundElementNumber;
                param.NextForegroundElementNumber++;
            
                Case.getInstance().getSpriteManager().AddTexture(textureId, textureFileName);
            }
            else if (token.equals("AnchorPosition"))
            {
                anchorPosition = Integer.parseInt(GetNextToken());
                EndLine();
            }
            else if (token.equals("Name"))
            {
                name = GetRestOfLine();
            }
            else if (token.equals("InteractionLocation"))
            {
                interactionLocation = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
            
                EndLine();
            }
            else if (token.equals("InteractFromAnywhere"))
            {
                interactFromAnywhere = true;
                
                EndLine();
            }
            else if (token.equals("ZOrderLine"))
            {
                Vector2 position1 = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                Vector2 position2 = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                zOrderLine = new Line (position1, position2.subtract(position1));
                EndLine();
            }
            else if (token.equals("BeginAnimation"))
            {
                Vector2 animationPosition = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                String animationId = location.getId() + "ForegroundElementAnimation" + param.NextForegroundElementAnimationNumber;
                param.NextForegroundElementAnimationNumber++;
                
                ParseAnimation(animationId, "EndAnimation");
                
                foregroundElementAnimationList.add(new ForegroundElementAnimation(animationId, animationPosition));
            }
            else if (token.equals("BeginHitBox"))
            {
                hitBox = ParseHitBox();
            }
            else if (token.equals("ClickPolygon"))
            {
                clickPolygon = ParsePolygon();
            }
            else if (token.equals("BeginCondition"))
            {
                condition = ParseCondition();
            }
            else if (token.equals("BeginConversation"))
            {
                conversation = ParseConversation();
            }
            else if (token.equals("ZoomedViewId"))
            {
                zoomedViewId = GetNextToken();
                EndLine();
            }
            else if (token.equals("EndForegroundElement"))
            {
                ForegroundElement foregroundElement = new ForegroundElement(textureId, position, anchorPosition, zOrderLine, foregroundElementAnimationList, hitBox, clickPolygon, conversation, zoomedViewId);
                foregroundElement.setName(name);
                foregroundElement.setInteractionLocation(interactionLocation);
                foregroundElement.setInteractFromAnywhere(interactFromAnywhere);
                foregroundElement.setCondition(condition);
                location.AddForegroundElement(foregroundElement);
                foregroundElementComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseHiddenForegroundElement(Location location, NextNumberParameter param) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean hiddenForegroundElementComplete = false;

        Vector2 position = new Vector2(0, 0);
        String textureId = null;
        String name = null;
        Polygon clickPolygon = new Polygon();
        String requiredPartnerId = null;
        Condition condition = null;
        Conversation conversation = null;
        String zoomedViewId = null;
        boolean isRequired = false;

        EndLine();

        while (!hiddenForegroundElementComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndHiddenForegroundElement found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Visual"))
            {
                position = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                String textureFileName = GetRestOfLine();
                textureId = location.getId() + "ForegroundElement" + param.NextForegroundElementNumber;
                param.NextForegroundElementNumber++;
            
                Case.getInstance().getSpriteManager().AddTexture(textureId, textureFileName);
            }
            else if (token.equals("Name"))
            {
                name = GetRestOfLine();
            }
            else if (token.equals("ClickPolygon"))
            {
                clickPolygon = ParsePolygon();
            }
            else if (token.equals("RequirePartner"))
            {
                requiredPartnerId = GetNextToken();
                EndLine();
            }
            else if (token.equals("IsRequired"))
            {
                isRequired = true;
                EndLine();
            }
            else if (token.equals("BeginCondition"))
            {
                condition = ParseCondition();
            }
            else if (token.equals("BeginConversation"))
            {
                conversation = ParseConversation();
            }
            else if (token.equals("ZoomedViewId"))
            {
                zoomedViewId = GetNextToken();
                EndLine();
            }
            else if (token.equals("EndHiddenForegroundElement"))
            {
                HiddenForegroundElement hiddenForegroundElement = new HiddenForegroundElement(textureId, position, clickPolygon, conversation, zoomedViewId, requiredPartnerId);
                location.AddHiddenForegroundElement(hiddenForegroundElement);
                hiddenForegroundElement.setName(name);
                hiddenForegroundElement.setIsRequired(isRequired);
                hiddenForegroundElement.setCondition(condition);
                hiddenForegroundElementComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseZoomedViewList(Location location, NextNumberParameter param) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean zoomedViewsComplete = false;

        EndLine();

        while (!zoomedViewsComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndZoomedViewList found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginZoomedView"))
            {
                ParseZoomedView(location, param);
            }
            else if (token.equals("EndZoomedViewList"))
            {
                zoomedViewsComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseZoomedView(Location location, NextNumberParameter param) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean zoomedViewComplete = false;
        ZoomedView zoomedView = new ZoomedView(GetNextToken());

        EndLine();

        while (!zoomedViewComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndZoomedView found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Background"))
            {
                String textureFileName = GetRestOfLine();
                String textureId = location.getId() + "ZoomedViewBackground" + param.NextZoomedViewBackgroundNumber;
                param.NextZoomedViewBackgroundNumber++;
            
                Case.getInstance().getSpriteManager().AddTexture(textureId, textureFileName);
                zoomedView.setBackgroundTextureId(textureId);
            }
            else if (token.equals("BeginForegroundElementList"))
            {
                ParseForegroundElementList(zoomedView, location, param);
            }
            else if (token.equals("EndZoomedView"))
            {
                zoomedViewComplete = true;
                location.AddZoomedView(zoomedView);
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseForegroundElementList(ZoomedView zoomedView, Location location, NextNumberParameter param) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean foregroundElementsComplete = false;

        EndLine();

        while (!foregroundElementsComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndForegroundElements found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginForegroundElement"))
            {
                ParseForegroundElement(zoomedView, location, param);
            }
            else if (token.equals("EndForegroundElementList"))
            {
                foregroundElementsComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseForegroundElement(ZoomedView parentZoomedView, Location location, NextNumberParameter param) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean foregroundElementComplete = false;

        String textureId = null;
        Vector2 position = new Vector2(0, 0);
        HitBox hitBox = null;
        Polygon clickPolygon = new Polygon();
        int anchorPosition = -1;
        String name = null;
        Line zOrderLine = null;
        Condition condition = null;
        Conversation conversation = null;
        String zoomedViewId = null;
        
        ArrayList<ForegroundElementAnimation> foregroundElementAnimationList = new ArrayList<ForegroundElementAnimation>();

        EndLine();

        while (!foregroundElementComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndForegroundElement found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Visual"))
            {
                position = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                String textureFileName = GetRestOfLine();
                textureId = location.getId() + "ForegroundElement" + param.NextForegroundElementNumber;
                param.NextForegroundElementNumber++;
            
                Case.getInstance().getSpriteManager().AddTexture(textureId, textureFileName);
            }
            else if (token.equals("AnchorPosition"))
            {
                anchorPosition = Integer.parseInt(GetNextToken());
                EndLine();
            }
            else if (token.equals("Name"))
            {
                name = GetRestOfLine();
            }
            else if (token.equals("ZOrderLine"))
            {
                Vector2 position1 = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                Vector2 position2 = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                zOrderLine = new Line (position1, position2.subtract(position1));
                EndLine();
            }
            else if (token.equals("BeginAnimation"))
            {
                Vector2 animationPosition = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                String animationId = location.getId() + "ForegroundElementAnimation" + param.NextForegroundElementAnimationNumber;
                param.NextForegroundElementAnimationNumber++;
                
                ParseAnimation(animationId, "EndAnimation");
                
                foregroundElementAnimationList.add(new ForegroundElementAnimation(animationId, animationPosition));
            }
            else if (token.equals("BeginHitBox"))
            {
                hitBox = ParseHitBox();
            }
            else if (token.equals("ClickPolygon"))
            {
                clickPolygon = ParsePolygon();
            }
            else if (token.equals("BeginCondition"))
            {
                condition = ParseCondition();
            }
            else if (token.equals("BeginConversation"))
            {
                conversation = ParseConversation();
            }
            else if (token.equals("ZoomedViewId"))
            {
                zoomedViewId = GetNextToken();
                EndLine();
            }
            else if (token.equals("EndForegroundElement"))
            {
                ForegroundElement foregroundElement = new ForegroundElement(textureId, position, anchorPosition, zOrderLine, foregroundElementAnimationList, hitBox, clickPolygon, conversation, zoomedViewId);
                foregroundElement.setName(name);
                foregroundElement.setCondition(condition);
                parentZoomedView.AddForegroundElement(foregroundElement);
                foregroundElementComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseCutscenes(Location location) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean cutscenesComplete = false;

        EndLine();

        while (!cutscenesComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCutscenes found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginCutscene"))
            {
                ParseCutscene(location);
            }
            else if (token.equals("EndCutscenes"))
            {
                cutscenesComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }
    
    static int cutsceneConfrontationCount = 0;

    private static void ParseCutscene(Location location) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        String cutsceneId = location.getId() + GetNextToken();
        boolean cutsceneComplete = false;

        FieldCutscene cutscene = new FieldCutscene();
        cutscene.setId(cutsceneId);
        int nextBackgroundIndex = 1;

        EndLine();

        while (!cutsceneComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCutscene found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("EnableAtStart"))
            {
                cutscene.setIsEnabled(true);
                EndLine();
            }
            else if (token.equals("AddCharacter"))
            {
                String characterId = GetNextToken();
                String partnerCharacterId = null;
                
                if (characterId.equals("PartnerCharacter"))
                {
                    partnerCharacterId = GetNextToken();
                }
                
                CharacterDirection characterDirection = ParseCharacterDirection();
                FieldCharacterDirection fieldCharacterDirection = ParseFieldCharacterDirection();
                Vector2 characterPosition = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                
                Case.getInstance().AddFieldCutsceneAddCharacterDependencyCheck(characterId, partnerCharacterId, characterDirection, fieldCharacterDirection, characterPosition, cutscene);
            
                EndLine();
            }
            else if (token.equals("SetBackground"))
            {
                int msFadeDuration = Integer.parseInt(GetNextToken());
                String backgroundFilePath = GetRestOfLine();
                
                if (backgroundFilePath.equals("None"))
                {
                    cutscene.SetBackground(null, msFadeDuration);
                }
                else
                {
                    String backgroundTextureId = cutsceneId + "Background" + nextBackgroundIndex;
                    nextBackgroundIndex++;

                    Case.getInstance().getSpriteManager().AddTexture(backgroundTextureId, backgroundFilePath);
                    cutscene.SetBackground(backgroundTextureId, msFadeDuration);
                }
            }
            else if (token.equals("BeginConversation"))
            {
                Conversation conversation = ParseConversation();
                cutscene.AddConversation(conversation);
            }
            else if (token.equals("BeginConfrontation"))
            {
                cutsceneConfrontationCount++;
                Confrontation confrontation = ParseConfrontation();
                cutscene.AddConversation(confrontation);
            }
            else if (token.equals("WalkCharacter"))
            {
                String movingCharacterId = GetNextToken();
                Vector2 targetPosition = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                cutscene.AddMovement(movingCharacterId, targetPosition, FieldCharacterState.Walking);
                EndLine();
            }
            else if (token.equals("RunCharacter"))
            {
                String movingCharacterId = GetNextToken();
                Vector2 targetPosition = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                cutscene.AddMovement(movingCharacterId, targetPosition, FieldCharacterState.Running);
                EndLine();
            }
            else if (token.equals("OrientCharacter"))
            {
                String turningCharacterId = GetNextToken();
                CharacterDirection direction = ParseCharacterDirection();
                FieldCharacterDirection spriteDirection = ParseFieldCharacterDirection();
                cutscene.AddOrient(turningCharacterId, direction, spriteDirection);
                EndLine();
            }
            else if (token.equals("BeginConcurrentMovements"))
            {
                ParseConcurrentMovements(cutscene);
            }
            else if (token.equals("InitialCameraPosition"))
            {
                cutscene.setInitialCameraPosition(new Vector2(Double.parseDouble(GetNextToken()), Double.parseDouble(GetNextToken())));
                EndLine();
            }
            else if (token.equals("Pause"))
            {
                int msDuration = Integer.parseInt(GetNextToken());
                cutscene.Pause(msDuration);
                
                EndLine();
            }
            else if (token.equals("CenterCamera"))
            {
                String firstToken = GetNextToken();
                double xCoordinate = 0;
                
                try
                {
                    xCoordinate = Double.parseDouble(firstToken);
                    double yCoordinate = Double.parseDouble(GetNextToken());

                    cutscene.CenterCamera(new Vector2(xCoordinate, yCoordinate));
                }
                catch (NumberFormatException ex)
                {
                    // If we hit a NumberFormatException, then
                    // that means we have a character ID instead
                    // of a coordinate.
                    cutscene.CenterCamera(firstToken);
                }
                
                EndLine();
            }
            else if (token.equals("PlayBgm"))
            {
                String bgmId = GetNextToken();
                cutscene.PlayBgm(bgmId);
                EndLine();
                
                Case.getInstance().getAudioManager().RequestBgm(bgmId);
            }
            else if (token.equals("StopBgm"))
            {
                String instantToken = GetNextToken();
                boolean isInstant = Utils.equals(instantToken, "Instantly");
                
                cutscene.StopBgm(isInstant);
                EndLine();
            }
            else if (token.equals("PreventInitialBgm"))
            {
                cutscene.PreventInitialBgm();
                EndLine();
            }
            else if (token.equals("ReplaceInitialBgm"))
            {
                String bgmId = GetNextToken();
                cutscene.ReplaceInitialBgm(bgmId);
                EndLine();
                
                Case.getInstance().getAudioManager().RequestBgm(bgmId);
            }
            else if (token.equals("PlayAmbiance"))
            {
                String ambianceSfxId = GetNextToken();
                cutscene.PlayAmbiance(ambianceSfxId);
                EndLine();
            }
            else if (token.equals("StopAmbiance"))
            {
                String instantToken = GetNextToken();
                boolean isInstant = Utils.equals(instantToken, "Instantly");
                
                cutscene.StopAmbiance(isInstant);
                EndLine();
            }
            else if (token.equals("PreventInitialAmbiance"))
            {
                cutscene.PreventInitialAmbiance();
                EndLine();
            }
            else if (token.equals("ReplaceInitialAmbiance"))
            {
                cutscene.ReplaceInitialAmbiance(GetNextToken());
                EndLine();
            }
            else if (token.equals("EndCase"))
            {
                cutscene.EndCase();
                EndLine();
            }
            else if (token.equals("CompleteCase"))
            {
                cutscene.CompleteCase();
                EndLine();
            }
            else if (token.equals("EndCutscene"))
            {
                Case.getInstance().getFieldCutsceneManager().AddCutscene(cutsceneId, cutscene);
                location.getCutsceneIdList().add(cutsceneId);
                cutsceneComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseConcurrentMovements(FieldCutscene cutscene) throws CaseParseException
    {
        String token = null;
        boolean concurrentMovementsComplete = false;

        EndLine();
        
        cutscene.BeginConcurrentMovements();

        while (!concurrentMovementsComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndConcurrentMovements found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("WalkCharacter"))
            {
                String movingCharacterId = GetNextToken();
                Vector2 targetPosition = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                cutscene.ConcurrentMovementsAddMovement(movingCharacterId, targetPosition, FieldCharacterState.Walking);
                EndLine();
            }
            else if (token.equals("RunCharacter"))
            {
                String movingCharacterId = GetNextToken();
                Vector2 targetPosition = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
                cutscene.ConcurrentMovementsAddMovement(movingCharacterId, targetPosition, FieldCharacterState.Running);
                EndLine();
            }
            else if (token.equals("OrientCharacter"))
            {
                String turningCharacterId = GetNextToken();
                CharacterDirection direction = ParseCharacterDirection();
                FieldCharacterDirection spriteDirection = ParseFieldCharacterDirection();
                cutscene.ConcurrentMovementsAddOrient(turningCharacterId, direction, spriteDirection);
                EndLine();
            }
            else if (token.equals("CenterCamera"))
            {
                String firstToken = GetNextToken();
                double xCoordinate = 0;
                
                try
                {
                    xCoordinate = Double.parseDouble(firstToken);
                    double yCoordinate = Double.parseDouble(GetNextToken());

                    cutscene.ConcurrentMovementsAddCenterCamera(new Vector2(xCoordinate, yCoordinate));
                }
                catch (NumberFormatException ex)
                {
                    // If we hit a NumberFormatException, then
                    // that means we have a character ID instead
                    // of a coordinate.
                    cutscene.ConcurrentMovementsAddCenterCamera(firstToken);
                }
                
                EndLine();
            }
            else if (token.equals("Wait"))
            {
                int msWait = Integer.parseInt(GetNextToken());
                cutscene.ConcurrentMovementsAddMillisecondDelayBeforeBegin(msWait);
                EndLine();
            }
            else if (token.equals("EndConcurrentMovements"))
            {
                cutscene.ConcurrentMovementsSetMillisecondDelayBeforeBegin(0);
                cutscene.FinishConcurrentMovements();
                concurrentMovementsComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseEncounter() throws CaseParseException, SlickException, IOException
    {
        Encounter encounter = new Encounter();
        String token = null;
        boolean encounterComplete = false;
        boolean seenConversation = false;
        boolean seenPresentEvidenceList = false;

        encounter.setId(GetNextToken());

        if (encounter.getId() == null)
        {
            throw new CaseParseException("Encounter must have an ID.");
        }

        String backgroundTextureId = encounter.getId() + "Background";

        EndLine();

        while (!encounterComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndContent found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("InitialLeftCharacter"))
            {
                String initialLeftCharacterId = GetNextToken();
                String initialLeftCharacterEmotionId = GetNextToken();
            
                encounter.setInitialLeftCharacterId(initialLeftCharacterId);
                encounter.setInitialLeftCharacterEmotionId(initialLeftCharacterEmotionId != null ? initialLeftCharacterEmotionId : "Neutral");
                EndLine();
            }
            else if (token.equals("InitialRightCharacter"))
            {
                String initialRightCharacterId = GetNextToken();
                String initialRightCharacterEmotionId = GetNextToken();
            
                encounter.setInitialRightCharacterId(initialRightCharacterId);
                encounter.setInitialRightCharacterEmotionId(initialRightCharacterEmotionId != null ? initialRightCharacterEmotionId : "Neutral");
                EndLine();
            }
            else if (token.equals("InitialConversation"))
            {
                encounter.setInitialConversationId(GetNextToken());
                EndLine();
            }
            else if (token.equals("OneShotConversation"))
            {
                encounter.setOneShotConversationId(GetNextToken());
                EndLine();
            }
            else if (token.equals("BeginConversation"))
            {
                ParseConversation(encounter);
                seenConversation = true;
            }
            else if (token.equals("BeginInterrogation"))
            {
                ParseInterrogation(encounter);
                seenConversation = true;
            }
            else if (token.equals("BeginConfrontation"))
            {
                ParseConfrontation(encounter);
                seenConversation = true;
            }
            else if (token.equals("BeginPresentEvidenceList"))
            {
                ParsePresentEvidenceList(encounter);
                seenPresentEvidenceList = true;
            }
            else if (token.equals("EndEncounter"))
            {
                /*if (!seenConversation)
                {
                    throw new CaseParseException("Encounter '" + encounter.getId() + "' must have at least one associated conversation or interrogation.");
                }*/
            
                if (!seenPresentEvidenceList)
                {
                    throw new CaseParseException("Encounter '" + encounter.getId() + "' must have a list of reactions to evidence presented.");
                }
            
                Case.getInstance().getContent().AddEncounter(encounter.getId(), encounter);
                encounterComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static Conversation ParseConversation() throws CaseParseException
    {
        return ParseConversation("EndConversation");
    }

    private static Conversation ParseConversation(String endToken) throws CaseParseException
    {
        Conversation conversation = new Conversation();

        EndLine();
        ParseConversationCore(conversation, endToken);

        return conversation;
    }

    private static void ParseConversation(Encounter encounter) throws CaseParseException
    {
        Conversation conversation = new Conversation();
        conversation.setId(GetNextToken());
        conversation.setName(GetRestOfLine());

        if (conversation.getId() == null)
        {
            throw new CaseParseException("Conversation must have an ID.");
        }
        else if (conversation.getName() == null)
        {
            throw new CaseParseException("Conversation '" + conversation.getId() + "' must have a name.");
        }

        ParseConversationCore(conversation, "EndConversation");
        Case.getInstance().getContent().AddConversation(conversation.getId(), conversation);
        encounter.AddConversation(conversation);
    }

    private static void ParseConversationCore(Conversation conversation, String endToken) throws CaseParseException
    {
        String token = null;
        BooleanByRefParameter conversationCompleteParam = new BooleanByRefParameter();

        while (!conversationCompleteParam.Value)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndConversation found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            ParseConversationInnerElement(token, conversation, endToken, conversationCompleteParam);
        }
    }

    private static void ParseConversationInnerElement(String token, Conversation conversation, String endToken, BooleanByRefParameter param) throws CaseParseException
    {
        if (token.equals("EnableAtStart"))
        {
            conversation.setIsEnabled(true);
            EndLine();
        }
        else if (token.equals("FinishAtStart"))
        {
            conversation.setHasBeenCompleted(true);
            EndLine();
        }
        else if (token.equals("UnlockTo"))
        {
            conversation.setUnlockToId(GetNextToken());
            EndLine();
        }
        else if (token.equals("RequirePartner"))
        {
            conversation.setRequiredPartnerId(GetNextToken());
            EndLine();
        }
        else if (token.equals("ChangeCharacter"))
        {
            ParseChangeCharacter(conversation);
        }
        else if (token.equals("ShowDialog"))
        {
            ParseShowDialog(conversation);
        }
        else if (token.equals("ShowDialogAutomatic"))
        {
            ParseShowDialogAutomatic(conversation);
        }
        else if (token.equals("SetFlag"))
        {
            ParseSetFlag(conversation);
        }
        else if (token.equals("BeginBranchOnCondition"))
        {
            ParseBeginBranchOnCondition(conversation);
        }
        else if (token.equals("BranchIfTrue"))
        {
            ParseBranchIfTrue(conversation);
        }
        else if (token.equals("BranchIfFalse"))
        {
            ParseBranchIfFalse(conversation);
        }
        else if (token.equals("EndBranchOnCondition"))
        {
            ParseEndBranchOnCondition(conversation);
        }
        else if (token.equals("MustPresentEvidence"))
        {
            ParseMustPresentEvidence(conversation);
        }
        else if (token.equals("CorrectEvidence"))
        {
            ParseCorrectEvidence(conversation);
        }
        else if (token.equals("EndMustPresentEvidence"))
        {
            ParseEndMustPresentEvidence(conversation);
        }
        else if (token.equals("BeginWrongEvidencePresented"))
        {
            ParseBeginWrongEvidencePresented(conversation);
        }
        else if (token.equals("EndWrongEvidencePresented"))
        {
            ParseEndWrongEvidencePresented(conversation);
        }
        else if (token.equals("BeginEndRequested"))
        {
            ParseBeginEndRequested(conversation);
        }
        else if (token.equals("EndEndRequested"))
        {
            ParseEndEndRequested(conversation);
        }
        else if (token.equals("EnableConversation"))
        {
            ParseEnableConversation(conversation);
        }
        else if (token.equals("EnableEvidence"))
        {
            ParseEnableEvidence(conversation);
        }
        else if (token.equals("UpdateEvidence"))
        {
            ParseUpdateEvidence(conversation);
        }
        else if (token.equals("DisableEvidence"))
        {
            ParseDisableEvidence(conversation);
        }
        else if (token.equals("EnableCutscene"))
        {
            ParseEnableCutscene(conversation);
        }
        else if (token.equals("PlayBgm"))
        {
            ParsePlayBgm(conversation);
        }
        else if (token.equals("PlayBgmPermanently"))
        {
            ParsePlayBgmPermanently(conversation);
        }
        else if (token.equals("PauseBgm"))
        {
            ParsePauseBgm(conversation);
        }
        else if (token.equals("ResumeBgm"))
        {
            ParseResumeBgm(conversation);
        }
        else if (token.equals("StopBgm"))
        {
            ParseStopBgm(conversation);
        }
        else if (token.equals("StopBgmPermanently"))
        {
            ParseStopBgmPermanently(conversation);
        }
        else if (token.equals("PlayAmbiance"))
        {
            ParsePlayAmbiance(conversation);
        }
        else if (token.equals("PlayAmbiancePermanently"))
        {
            ParsePlayAmbiancePermanently(conversation);
        }
        else if (token.equals("PauseAmbiance"))
        {
            ParsePauseAmbiance(conversation);
        }
        else if (token.equals("ResumeAmbiance"))
        {
            ParseResumeAmbiance(conversation);
        }
        else if (token.equals("StopAmbiance"))
        {
            ParseStopAmbiance(conversation);
        }
        else if (token.equals("StopAmbiancePermanently"))
        {
            ParseStopAmbiancePermanently(conversation);
        }
        else if (token.equals("StartAnimation"))
        {
            ParseStartAnimation(conversation);
        }
        else if (token.equals("StopAnimation"))
        {
            ParseStopAnimation(conversation);
        }
        else if (token.equals("SetPartner"))
        {
            ParseSetPartner(conversation);
        }
        else if (token.equals("GoToPresentWrongEvidence"))
        {
            ParseGoToPresentWrongEvidence(conversation);
        }
        else if (token.equals("LockConversation"))
        {
            ParseLockConversation(conversation);
        }
        else if (token.equals("BeginMonologue"))
        {
            ParseBeginMonologue(conversation);
        }
        else if (token.equals("EndMonologue"))
        {
            ParseEndMonologue(conversation);
        }
        else if (token.equals("ExitEncounter"))
        {
            ParseExitEncounter(conversation);
        }
        else if (token.equals("MoveToLocation"))
        {
            ParseMoveToLocation(conversation);
        }
        else if (token.equals("MoveToZoomedView"))
        {
            ParseMoveToZoomedView(conversation);
        }
        else if (token.equals("EndCase"))
        {
            ParseEndCase(conversation);
        }
        else if (token.equals("CompleteCase"))
        {
            ParseCompleteCase(conversation);
        }
        else if (token.equals("BeginMultipleChoice"))
        {
            ParseBeginMultipleChoice(conversation);
        }
        else if (token.equals("BeginMultipleChoiceOption"))
        {
            ParseBeginMultipleChoiceOption(conversation);
        }
        else if (token.equals("ExitMultipleChoice"))
        {
            ParseExitMultipleChoice(conversation);
        }
        else if (token.equals("EndMultipleChoiceOption"))
        {
            ParseEndMultipleChoiceOption(conversation);
        }
        else if (token.equals("EndMultipleChoice"))
        {
            ParseEndMultipleChoice(conversation);
        }
        else if (token.equals("EnableFastForward"))
        {
            ParseEnableFastForward(conversation);
        }
        else if (token.equals("DisableFastForward"))
        {
            ParseDisableFastForward(conversation);
        }
        else if (token.equals("BeginBreakdown"))
        {
            ParseBeginBreakdown(conversation);
        }
        else if (token.equals("EndBreakdown"))
        {
            ParseEndBreakdown(conversation);
        }
        else if (token.equals("InsertConversationSnippet"))
        {
            ParseInsertConversationSnippet(conversation);
        }
        else
        {
            if (token.equals(endToken))
            {
                param.Value = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ParseChangeCharacter(Conversation conversation) throws CaseParseException
    {
        CharacterPosition characterPosition = ParseCharacterPosition();
        String characterId = GetNextToken();
        String initialEmotionId = GetNextToken();
        String sfxId = GetNextToken();
        
        if (sfxId == null && initialEmotionId != null)
        {
            // initialEmotionId might be a SFX in this case, so let's check.
            if (SoundSystem.getInstance().getAllSoundIDs().contains(initialEmotionId))
            {
                sfxId = initialEmotionId;
                initialEmotionId = null;
            }
        }

        conversation.ChangeCharacter(characterPosition, characterId, initialEmotionId, sfxId);
        EndLine();
    }

    private static void ParseShowDialog(Conversation conversation) throws CaseParseException
    {
        String nextToken = GetNextToken();
        CharacterPosition characterPosition = null;
        String characterId = null;
        String filePath = null;
        int leadInTime = 0;
        String rawDialog = "";
        
        try
        {
            characterPosition = CharacterPosition.valueOf(nextToken);
        }
        catch (Exception ex)
        {
            characterPosition = CharacterPosition.Offscreen;
            characterId = nextToken;
        }
        
        nextToken = GetNextToken();
        
        if (nextToken == null)
        {
            nextToken = "";
        }
        
        // If the next token is "Audio", then a file path follows it.
        // On the other hand, if it's not, then it's the start of the dialog.
        if (nextToken.equals("Audio"))
        {
            filePath = GetNextToken();
            
            Case.getInstance().getAudioManager().AddDialogFilePath(filePath);
            
            nextToken = GetNextToken();
            
            if (nextToken == null)
            {
                nextToken = "";
            }
            
            if (nextToken.equals("LeadIn"))
            {
                leadInTime = Integer.parseInt(GetNextToken());
            }
            else
            {
                rawDialog = nextToken + " ";
            }
        }
        else
        {
            rawDialog = nextToken + " ";
        }
        
        rawDialog += GetRestOfLine();

        conversation.ShowDialog(characterPosition, characterId, rawDialog, filePath, leadInTime);
    }

    private static void ParseShowDialogAutomatic(Conversation conversation) throws CaseParseException
    {
        int delayBeforeContinuing = Integer.parseInt(GetNextToken());
        String nextToken = GetNextToken();
        CharacterPosition characterPosition = null;
        String characterId = null;
        String filePath = null;
        int leadInTime = 0;
        String rawDialog = "";
        
        try
        {
            characterPosition = CharacterPosition.valueOf(nextToken);
        }
        catch (Exception ex)
        {
            characterPosition = CharacterPosition.Offscreen;
            characterId = nextToken;
        }
        
        nextToken = GetNextToken();
        
        if (nextToken == null)
        {
            nextToken = "";
        }
        
        // If the next token is "Audio", then a file path follows it.
        // On the other hand, if it's not, then it's the start of the dialog.
        if (nextToken.equals("Audio"))
        {
            filePath = GetNextToken();
            
            Case.getInstance().getAudioManager().AddDialogFilePath(filePath);
            
            nextToken = GetNextToken();
            
            if (nextToken == null)
            {
                nextToken = "";
            }
            
            if (nextToken.equals("LeadIn"))
            {
                leadInTime = Integer.parseInt(GetNextToken());
            }
            else
            {
                rawDialog = nextToken + " ";
            }
        }
        else
        {
            rawDialog = nextToken + " ";
        }
        
        rawDialog += GetRestOfLine();

        conversation.ShowDialogAutomatic(characterPosition, characterId, rawDialog, filePath, leadInTime, delayBeforeContinuing);
    }

    private static void ParseSetFlag(Conversation conversation) throws CaseParseException
    {
        String flagId = GetNextToken();
        String notificationString = GetRestOfLine();

        conversation.SetFlag(flagId, notificationString);
    }

    private static void ParseBeginBranchOnCondition(Conversation conversation) throws CaseParseException
    {
        EndLine();
        String nextToken = GetNextToken();
        
        if (!nextToken.equals("BeginCondition"))
        {
            throw new CaseParseException("BeginBranchOnCondition must be immediately followed by BeginCondition.");
        }
        
        Condition condition = ParseCondition();
        conversation.BeginBranchOnCondition(condition);
    }

    private static void ParseBranchIfTrue(Conversation conversation) throws CaseParseException
    {
        conversation.BranchIfTrue();
        EndLine();
    }

    private static void ParseBranchIfFalse(Conversation conversation) throws CaseParseException
    {
        conversation.BranchIfFalse();
        EndLine();
    }

    private static void ParseEndBranchOnCondition(Conversation conversation) throws CaseParseException
    {
        conversation.EndBranchOnCondition();
        EndLine();
    }

    private static void ParseMustPresentEvidence(Conversation conversation) throws CaseParseException
    {
        String nextToken = GetNextToken();
        String filePath = null;
        int leadInTime = 0;
        String rawDialog = "";
        CharacterPosition characterPosition = null;
        String characterId = null;
        
        try
        {
            characterPosition = CharacterPosition.valueOf(nextToken);
        }
        catch (Exception ex)
        {
            characterPosition = CharacterPosition.Offscreen;
            characterId = nextToken;
        }
        
        nextToken = GetNextToken();
        
        if (nextToken == null)
        {
            nextToken = "";
        }
        
        // If the next token is "Audio", then a file path follows it.
        // On the other hand, if it's not, then it's the start of the dialog.
        if (nextToken.equals("Audio"))
        {
            filePath = GetNextToken();
            
            Case.getInstance().getAudioManager().AddDialogFilePath(filePath);
            
            nextToken = GetNextToken();
            
            if (nextToken == null)
            {
                nextToken = "";
            }
            
            if (nextToken.equals("LeadIn"))
            {
                leadInTime = Integer.parseInt(GetNextToken());
            }
            else
            {
                rawDialog = nextToken + " ";
            }
        }
        else
        {
            rawDialog = nextToken + " ";
        }
        
        rawDialog += GetRestOfLine();

        conversation.MustPresentEvidence(characterPosition, characterId, rawDialog, filePath, leadInTime);
    }
    
    private static void ParseCorrectEvidence(Conversation conversation) throws CaseParseException
    {
        String correctEvidenceId = GetNextToken();
        conversation.AddCorrectEvidenceId(correctEvidenceId);
        EndLine();
    }

    private static void ParseEndMustPresentEvidence(Conversation conversation) throws CaseParseException
    {
        conversation.EndMustPresentEvidence();
        EndLine();
    }

    private static void ParseBeginWrongEvidencePresented(Conversation conversation) throws CaseParseException
    {
        conversation.BeginWrongEvidencePresented();
        EndLine();
    }

    private static void ParseEndWrongEvidencePresented(Conversation conversation) throws CaseParseException
    {
        conversation.EndWrongEvidencePresented();
        EndLine();
    }

    private static void ParseBeginEndRequested(Conversation conversation) throws CaseParseException
    {
        conversation.BeginEndRequested();
        EndLine();
    }

    private static void ParseEndEndRequested(Conversation conversation) throws CaseParseException
    {
        conversation.EndEndRequested();
        EndLine();
    }

    private static void ParseEnableConversation(Conversation conversation) throws CaseParseException
    {
        String conversationId = GetNextToken();
        String notifyString = GetNextToken();
        boolean notify = true;

        if (notifyString != null && notifyString.equals("NoNotify"))
        {
            notify = false;
        }
        else if (notifyString != null)
        {
            ThrowForUnknownToken(notifyString);
        }

        conversation.EnableConversation(conversationId, notify);
        EndLine();
    }

    private static void ParseEnableEvidence(Conversation conversation) throws CaseParseException
    {
        String evidenceId = GetNextToken();
        String notifyString = GetNextToken();
        boolean notify = true;

        if (notifyString != null && notifyString.equals("NoNotify"))
        {
            notify = false;
        }
        else if (notifyString != null)
        {
            ThrowForUnknownToken(notifyString);
        }

        conversation.EnableEvidence(evidenceId, notify);
        EndLine();
    }

    private static void ParseUpdateEvidence(Conversation conversation) throws CaseParseException
    {
        String evidenceId = GetNextToken();
        String newEvidenceId = GetNextToken();
        String notifyString = GetNextToken();
        boolean notify = true;

        if (notifyString != null && notifyString.equals("NoNotify"))
        {
            notify = false;
        }
        else if (notifyString != null)
        {
            ThrowForUnknownToken(notifyString);
        }

        conversation.UpdateEvidence(evidenceId, newEvidenceId, notify);
        EndLine();
    }

    private static void ParseDisableEvidence(Conversation conversation) throws CaseParseException
    {
        String evidenceId = GetNextToken();
        String notifyString = GetNextToken();
        boolean notify = true;

        if (notifyString != null && notifyString.equals("NoNotify"))
        {
            notify = false;
        }
        else if (notifyString != null)
        {
            ThrowForUnknownToken(notifyString);
        }

        conversation.DisableEvidence(evidenceId, notify);
        EndLine();
    }

    private static void ParseEnableCutscene(Conversation conversation) throws CaseParseException
    {
        conversation.EnableCutscene(GetNextToken());
        EndLine();
    }

    private static void ParsePlayBgm(Conversation conversation) throws CaseParseException
    {
        String bgmId = GetNextToken();

        conversation.PlayBgm(bgmId);
        EndLine();
    }

    private static void ParsePlayBgmPermanently(Conversation conversation) throws CaseParseException
    {
        String bgmId = GetNextToken();

        conversation.PlayBgm(bgmId, true /* preserveBgm */);
        EndLine();
    }

    private static void ParsePauseBgm(Conversation conversation) throws CaseParseException
    {
        conversation.PauseBgm();
        EndLine();
    }

    private static void ParseResumeBgm(Conversation conversation) throws CaseParseException
    {
        conversation.ResumeBgm();
        EndLine();
    }

    private static void ParseStopBgm(Conversation conversation) throws CaseParseException
    {
        String instantToken = GetNextToken();
        boolean isInstant = Utils.equals(instantToken, "Instantly");

        conversation.StopBgm(isInstant);
        EndLine();
    }

    private static void ParseStopBgmPermanently(Conversation conversation) throws CaseParseException
    {
        String instantToken = GetNextToken();
        boolean isInstant = Utils.equals(instantToken, "Instantly");

        conversation.StopBgm(isInstant, true /* preserveBgm */);
        EndLine();
    }

    private static void ParsePlayAmbiance(Conversation conversation) throws CaseParseException
    {
        String ambianceSfxId = GetNextToken();

        conversation.PlayAmbiance(ambianceSfxId);
        EndLine();
    }

    private static void ParsePlayAmbiancePermanently(Conversation conversation) throws CaseParseException
    {
        String ambianceSfxId = GetNextToken();

        conversation.PlayAmbiance(ambianceSfxId, true /* preserveBgm */);
        EndLine();
    }

    private static void ParsePauseAmbiance(Conversation conversation) throws CaseParseException
    {
        conversation.PauseAmbiance();
        EndLine();
    }

    private static void ParseResumeAmbiance(Conversation conversation) throws CaseParseException
    {
        conversation.ResumeAmbiance();
        EndLine();
    }

    private static void ParseStopAmbiance(Conversation conversation) throws CaseParseException
    {
        String instantToken = GetNextToken();
        boolean isInstant = Utils.equals(instantToken, "Instantly");

        conversation.StopAmbiance(isInstant);
        EndLine();
    }

    private static void ParseStopAmbiancePermanently(Conversation conversation) throws CaseParseException
    {
        String instantToken = GetNextToken();
        boolean isInstant = Utils.equals(instantToken, "Instantly");

        conversation.StopAmbiance(isInstant, true /* preserveBgm */);
        EndLine();
    }

    private static void ParseStartAnimation(Conversation conversation) throws CaseParseException
    {
        String animationId = GetNextToken();

        conversation.StartAnimation(animationId);
        EndLine();
    }

    private static void ParseStopAnimation(Conversation conversation) throws CaseParseException
    {
        conversation.StopAnimation();
        EndLine();
    }

    private static void ParseSetPartner(Conversation conversation) throws CaseParseException
    {
        String partnerId = GetNextToken();
        String notifyString = GetNextToken();
        boolean notify = true;

        if (notifyString != null && notifyString.equals("NoNotify"))
        {
            notify = false;
        }
        else if (notifyString != null)
        {
            ThrowForUnknownToken(notifyString);
        }

        conversation.SetPartner(partnerId, notify);
        EndLine();
    }

    private static void ParseGoToPresentWrongEvidence(Conversation conversation) throws CaseParseException
    {
        conversation.GoToPresentWrongEvidence();
        EndLine();
    }

    private static void ParseLockConversation(Conversation conversation) throws CaseParseException
    {
        String unlockCondition = GetNextToken();
        String unlockConditionParameter = GetNextToken();
        
        conversation.LockConversation(unlockCondition, unlockConditionParameter);
        EndLine();
    }

    private static void ParseBeginMonologue(Conversation conversation) throws CaseParseException
    {
        CharacterPosition characterPosition = ParseCharacterPosition();
        //conversation.BeginMonologue(characterPosition);
        EndLine();
    }

    private static void ParseEndMonologue(Conversation conversation) throws CaseParseException
    {
        //conversation.EndMonologue();
        EndLine();
    }

    private static void ParseExitEncounter(Conversation conversation) throws CaseParseException
    {
        conversation.ExitEncounter();
        EndLine();
    }

    private static void ParseMoveToLocation(Conversation conversation) throws CaseParseException
    {
        conversation.MoveToLocation(GetNextToken(), GetNextToken(), GetNextToken());
        EndLine();
    }

    private static void ParseMoveToZoomedView(Conversation conversation) throws CaseParseException
    {
        conversation.MoveToZoomedView(GetNextToken());
        EndLine();
    }

    private static void ParseEndCase(Conversation conversation) throws CaseParseException
    {
        conversation.EndCase();
        EndLine();
    }

    private static void ParseCompleteCase(Conversation conversation) throws CaseParseException
    {
        conversation.CompleteCase();
        EndLine();
    }

    private static void ParseBeginMultipleChoice(Conversation conversation) throws CaseParseException
    {
        conversation.BeginMultipleChoice();
        EndLine();
    }

    private static void ParseBeginMultipleChoiceOption(Conversation conversation) throws CaseParseException
    {
        String optionText = GetRestOfLine();
        conversation.BeginMultipleChoiceOption(optionText);
    }

    private static void ParseExitMultipleChoice(Conversation conversation) throws CaseParseException
    {
        conversation.ExitMultipleChoice();
        EndLine();
    }

    private static void ParseEndMultipleChoiceOption(Conversation conversation) throws CaseParseException
    {
        conversation.EndMultipleChoiceOption();
        EndLine();
    }

    private static void ParseEndMultipleChoice(Conversation conversation) throws CaseParseException
    {
        conversation.EndMultipleChoice();
        EndLine();
    }
    
    private static void ParseEnableFastForward(Conversation conversation) throws CaseParseException
    {
        conversation.EnableFastForward();
        EndLine();
    }
    
    private static void ParseDisableFastForward(Conversation conversation) throws CaseParseException
    {
        conversation.DisableFastForward();
        EndLine();
    }
    
    private static void ParseBeginBreakdown(Conversation conversation) throws CaseParseException
    {
        conversation.BeginBreakdown(ParseCharacterPosition());
        EndLine();
    }
    
    private static void ParseEndBreakdown(Conversation conversation) throws CaseParseException
    {
        conversation.EndBreakdown();
        EndLine();
    }

    private static void ParseInsertConversationSnippet(Conversation conversation) throws CaseParseException
    {
        String snippetId = GetNextToken();
        conversation.InsertConversationSnippet(snippetId);
        EndLine();
    }

    private static Interrogation ParseInterrogation() throws CaseParseException
    {
        Interrogation interrogation = new Interrogation();

        EndLine();
        ParseInterrogationCore(interrogation);

        return interrogation;
    }

    private static void ParseInterrogation(Encounter encounter) throws CaseParseException
    {
        Interrogation interrogation = new Interrogation();
        interrogation.setId(GetNextToken());
        interrogation.setName(GetRestOfLine());

        if (interrogation.getId() == null)
        {
            throw new CaseParseException("Interrogation must have an ID.");
        }
        else if (interrogation.getName() == null)
        {
            throw new CaseParseException("Interrogation '" + interrogation.getId() + "' must have a name.");
        }

        ParseInterrogationCore(interrogation);
        Case.getInstance().getContent().AddConversation(interrogation.getId(), interrogation);
        encounter.AddInterrogation(interrogation);
    }

    private static void ParseInterrogationCore(Interrogation interrogation) throws CaseParseException
    {
        String token = null;
        BooleanByRefParameter interrogationCompleteParam = new BooleanByRefParameter();

        while (!interrogationCompleteParam.Value)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndInterrogation found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            ParseInterrogationInnerElement(token, interrogation, interrogationCompleteParam);
        }
    }

    private static void ParseInterrogationInnerElement(String token, Interrogation interrogation, BooleanByRefParameter param) throws CaseParseException
    {
        if (token.equals("BeginInterrogationRepeat"))
        {
            ParseBeginInterrogationRepeat(interrogation);
        }
        else if (token.equals("ShowInterrogation"))
        {
            ParseShowInterrogation(interrogation);
        }
        else if (token.equals("ShowInterrogationConditional"))
        {
            ParseShowInterrogationConditional(interrogation);
        }
        else if (token.equals("BeginPressForInfo"))
        {
            ParseBeginPressForInfo(interrogation);
        }
        else if (token.equals("EndPressForInfo"))
        {
            ParseEndPressForInfo(interrogation);
        }
        else if (token.equals("BeginPresentEvidence"))
        {
            ParseBeginPresentEvidence(interrogation);
        }
        else if (token.equals("EndPresentEvidence"))
        {
            ParseEndPresentEvidence(interrogation);
        }
        else if (token.equals("BeginUsePartner"))
        {
            ParseBeginUsePartner(interrogation);
        }
        else if (token.equals("EndUsePartner"))
        {
            ParseEndUsePartner(interrogation);
        }
        else if (token.equals("EndShowInterrogationBranches"))
        {
            ParseEndShowInterrogationBranches(interrogation);
        }
        else if (token.equals("BeginWrongPartnerUsed"))
        {
            ParseBeginWrongPartnerUsed(interrogation);
        }
        else if (token.equals("EndWrongPartnerUsed"))
        {
            ParseEndWrongPartnerUsed(interrogation);
        }
        else if (token.equals("ExitInterrogationRepeat"))
        {
            ParseExitInterrogationRepeat(interrogation);
        }
        else if (token.equals("BeginEndRequested"))
        {
            ParseBeginEndRequested(interrogation);
        }
        else if (token.equals("EndEndRequested"))
        {
            ParseEndEndRequested(interrogation);
        }
        else if (token.equals("EndInterrogationRepeat"))
        {
            ParseEndInterrogationRepeat(interrogation);
        }
        else if (token.equals("EndInterrogation"))
        {
            param.Value = true;
            EndLine();
        }
        else
        {
            ParseConversationInnerElement(token, interrogation, null, param);
        }
    }

    private static void ParseBeginInterrogationRepeat(Interrogation interrogation) throws CaseParseException
    {
        interrogation.BeginInterrogationRepeat();
        EndLine();
    }

    private static void ParseShowInterrogation(Interrogation interrogation) throws CaseParseException
    {
        String nextToken = GetNextToken();
        String filePath = null;
        CharacterPosition characterPosition = null;
        String characterId = null;
        int leadInTime = 0;
        String rawDialog = "";
        
        try
        {
            characterPosition = CharacterPosition.valueOf(nextToken);
        }
        catch (Exception ex)
        {
            characterPosition = CharacterPosition.Offscreen;
            characterId = nextToken;
        }
        
        nextToken = GetNextToken();
        
        if (nextToken == null)
        {
            nextToken = "";
        }
        
        // If the next token is "Audio", then a file path follows it.
        // On the other hand, if it's not, then it's the start of the dialog.
        if (nextToken.equals("Audio"))
        {
            filePath = GetNextToken();
            
            Case.getInstance().getAudioManager().AddDialogFilePath(filePath);
            
            nextToken = GetNextToken();
            
            if (nextToken == null)
            {
                nextToken = "";
            }
            
            if (nextToken.equals("LeadIn"))
            {
                leadInTime = Integer.parseInt(GetNextToken());
            }
            else
            {
                rawDialog = nextToken + " ";
            }
        }
        else
        {
            rawDialog = nextToken + " ";
        }
        
        rawDialog += GetRestOfLine();

        interrogation.ShowInterrogation(characterPosition, characterId, rawDialog, filePath, leadInTime);
    }

    private static void ParseShowInterrogationConditional(Interrogation interrogation) throws CaseParseException
    {
        String conditionFlag = GetNextToken();
        String nextToken = GetNextToken();
        String filePath = null;
        CharacterPosition characterPosition = null;
        String characterId = null;
        int leadInTime = 0;
        String rawDialog = "";
        
        try
        {
            characterPosition = CharacterPosition.valueOf(nextToken);
        }
        catch (Exception ex)
        {
            characterPosition = CharacterPosition.Offscreen;
            characterId = nextToken;
        }
        
        nextToken = GetNextToken();
        
        if (nextToken == null)
        {
            nextToken = "";
        }
        
        // If the next token is "Audio", then a file path follows it.
        // On the other hand, if it's not, then it's the start of the dialog.
        if (nextToken.equals("Audio"))
        {
            filePath = GetNextToken();
            
            Case.getInstance().getAudioManager().AddDialogFilePath(filePath);
            
            nextToken = GetNextToken();
            
            if (nextToken == null)
            {
                nextToken = "";
            }
            
            if (nextToken.equals("LeadIn"))
            {
                leadInTime = Integer.parseInt(GetNextToken());
            }
            else
            {
                rawDialog = nextToken + " ";
            }
        }
        else
        {
            rawDialog = nextToken + " ";
        }
        
        rawDialog += GetRestOfLine();

        interrogation.ShowInterrogationConditional(conditionFlag, characterPosition, characterId, rawDialog, filePath, leadInTime);
    }

    private static void ParseBeginPressForInfo(Interrogation interrogation) throws CaseParseException
    {
        interrogation.BeginPressForInfo();
        EndLine();
    }

    private static void ParseEndPressForInfo(Interrogation interrogation) throws CaseParseException
    {
        interrogation.EndPressForInfo();
        EndLine();
    }

    private static void ParseBeginPresentEvidence(Interrogation interrogation) throws CaseParseException
    {
        String evidenceId = GetNextToken();

        interrogation.BeginPresentEvidence(evidenceId);
        EndLine();
    }

    private static void ParseEndPresentEvidence(Interrogation interrogation) throws CaseParseException
    {
        interrogation.EndPresentEvidence();
        EndLine();
    }

    private static void ParseBeginUsePartner(Interrogation interrogation) throws CaseParseException
    {
        String partnerId = GetNextToken();

        interrogation.BeginUsePartner(partnerId);
        EndLine();
    }

    private static void ParseEndUsePartner(Interrogation interrogation) throws CaseParseException
    {
        interrogation.EndUsePartner();
        EndLine();
    }

    private static void ParseEndShowInterrogationBranches(Interrogation interrogation) throws CaseParseException
    {
        interrogation.EndShowInterrogationBranches();
        EndLine();
    }

    private static void ParseBeginWrongPartnerUsed(Interrogation interrogation) throws CaseParseException
    {
        interrogation.BeginWrongPartnerUsed();
        EndLine();
    }

    private static void ParseEndWrongPartnerUsed(Interrogation interrogation) throws CaseParseException
    {
        interrogation.EndWrongPartnerUsed();
        EndLine();
    }

    private static void ParseExitInterrogationRepeat(Interrogation interrogation) throws CaseParseException
    {
        interrogation.ExitInterrogationRepeat();
        EndLine();
    }

    private static void ParseEndInterrogationRepeat(Interrogation interrogation) throws CaseParseException
    {
        interrogation.EndInterrogationRepeat();
        EndLine();
    }

    private static Confrontation ParseConfrontation() throws CaseParseException, SlickException, IOException
    {
        Confrontation confrontation = new Confrontation();
        confrontation.setId("CutsceneConfrontation" + cutsceneConfrontationCount);

        EndLine();
        ParseConfrontationCore(confrontation);

        return confrontation;
    }

    private static void ParseConfrontation(Encounter encounter) throws CaseParseException, SlickException, IOException
    {
        Confrontation confrontation = new Confrontation();
        confrontation.setId(GetNextToken());
        confrontation.setName(GetRestOfLine());

        if (confrontation.getId() == null)
        {
            throw new CaseParseException("Confrontation must have an ID.");
        }
        else if (confrontation.getName() == null)
        {
            throw new CaseParseException("Confrontation '" + confrontation.getId() + "' must have a name.");
        }

        ParseConfrontationCore(confrontation);
        Case.getInstance().getContent().AddConversation(confrontation.getId(), confrontation);
        encounter.AddConfrontation(confrontation);
    }

    private static void ParseConfrontationCore(Confrontation confrontation) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        BooleanByRefParameter confrontationCompleteParam = new BooleanByRefParameter();

        while (!confrontationCompleteParam.Value)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndConfrontation found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            ParseConfrontationInnerElement(token, confrontation, confrontationCompleteParam);
        }
    }

    private static void ParseConfrontationInnerElement(String token, Confrontation confrontation, BooleanByRefParameter param) throws CaseParseException, SlickException, IOException
    {
        if (token.equals("ConfrontationParticipants"))
        {
            String playerCharacterId = GetNextToken();
            String opponentCharacterId = GetNextToken();
            
            confrontation.SetParticipants(playerCharacterId, opponentCharacterId);
            EndLine();
        }
        else if (token.equals("PlayerIconOffset"))
        {
            Vector2 playerIconOffset = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
            confrontation.SetPlayerIconOffset(playerIconOffset);
            EndLine();
        }
        else if (token.equals("PlayerHealth"))
        {
            confrontation.SetPlayerHealth(Integer.parseInt(GetNextToken()));
            EndLine();
        }
        else if (token.equals("OpponentIconOffset"))
        {
            Vector2 opponentIconOffset = new Vector2(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
            confrontation.SetOpponentIconOffset(opponentIconOffset);
            EndLine();
        }
        else if (token.equals("OpponentHealth"))
        {
            confrontation.SetOpponentHealth(Integer.parseInt(GetNextToken()));
            EndLine();
        }
        else if (token.equals("BeginConfrontationTopicSelection"))
        {
            ParseBeginConfrontationTopicSelection(confrontation);
        }
        else if (token.equals("BeginInitialConfrontationTopic"))
        {
            ParseBeginInitialConfrontationTopic(confrontation);
        }
        else if (token.equals("EndInitialConfrontationTopic"))
        {
            ParseEndInitialConfrontationTopic(confrontation);
        }
        else if (token.equals("BeginConfrontationTopic"))
        {
            ParseBeginConfrontationTopic(confrontation);
        }
        else if (token.equals("EndConfrontationTopic"))
        {
            ParseEndConfrontationTopic(confrontation);
        }
        else if (token.equals("BeginPlayerDefeated"))
        {
            ParseBeginPlayerDefeated(confrontation);
        }
        else if (token.equals("EndPlayerDefeated"))
        {
            ParseEndPlayerDefeated(confrontation);
        }
        else if (token.equals("EndConfrontationTopicSelection"))
        {
            ParseEndConfrontationTopicSelection(confrontation);
        }
        else if (token.equals("EnableTopic"))
        {
            ParseEnableTopic(confrontation);
        }
        else if (token.equals("RestartConfrontation"))
        {
            ParseRestartConfrontation(confrontation);
        }
        else if (token.equals("EndConfrontation"))
        {
            param.Value = true;
            EndLine();
        }
        else
        {
            ParseInterrogationInnerElement(token, confrontation, param);
        }
    }

    private static void ParseBeginConfrontationTopicSelection(Confrontation confrontation) throws CaseParseException
    {
        EndLine();
        confrontation.BeginConfrontationTopicSelection();
    }

    private static void ParseBeginInitialConfrontationTopic(Confrontation confrontation) throws CaseParseException
    {
        EndLine();
        confrontation.BeginInitialConfrontationTopic();
    }

    private static void ParseEndInitialConfrontationTopic(Confrontation confrontation) throws CaseParseException
    {
        EndLine();
        confrontation.EndInitialConfrontationTopic();
    }

    private static void ParseBeginConfrontationTopic(Confrontation confrontation)
    {
        confrontation.BeginConfrontationTopic(GetNextToken(), GetRestOfLine());
    }

    private static void ParseEndConfrontationTopic(Confrontation confrontation) throws CaseParseException
    {
        EndLine();
        confrontation.EndConfrontationTopic();
    }

    private static void ParseBeginPlayerDefeated(Confrontation confrontation) throws CaseParseException
    {
        EndLine();
        confrontation.BeginPlayerDefeated();
    }

    private static void ParseEndPlayerDefeated(Confrontation confrontation) throws CaseParseException
    {
        EndLine();
        confrontation.EndPlayerDefeated();
    }

    private static void ParseEndConfrontationTopicSelection(Confrontation confrontation) throws CaseParseException
    {
        EndLine();
        confrontation.EndConfrontationTopicSelection();
    }

    private static void ParseEnableTopic(Confrontation confrontation) throws CaseParseException
    {
        String topicId = GetNextToken();
        String notifyString = GetNextToken();
        boolean notify = true;

        if (notifyString != null && notifyString.equals("NoNotify"))
        {
            notify = false;
        }
        else if (notifyString != null)
        {
            ThrowForUnknownToken(notifyString);
        }

        EndLine();
        confrontation.EnableTopic(topicId, notify);
    }

    private static void ParseRestartConfrontation(Confrontation confrontation) throws CaseParseException
    {
        EndLine();
        confrontation.RestartConfrontation();
    }

    private static void ParsePresentEvidenceList(Encounter encounter) throws CaseParseException
    {
        String token = null;
        boolean presentEvidenceListComplete = false;

        EndLine();

        while (!presentEvidenceListComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndPresentEvidenceList found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("BeginPresentEvidence"))
            {
                String evidenceId = GetNextToken();
                Conversation presentEvidenceConversation = ParseConversation("EndPresentEvidence");
            
                encounter.AddPresentEvidenceConversation(evidenceId, presentEvidenceConversation);
            }
            else if (token.equals("BeginPresentWrongEvidence"))
            {
                Conversation presentWrongEvidenceConversation = ParseConversation("EndPresentWrongEvidence");
            
                encounter.setPresentWrongEvidenceConversation(presentWrongEvidenceConversation);
            }
            else if (token.equals("BeginPresentWrongProfile"))
            {
                Conversation presentWrongProfileConversation = ParseConversation("EndPresentWrongProfile");
            
                encounter.setPresentWrongProfileConversation(presentWrongProfileConversation);
            }
            else if (token.equals("EndPresentEvidenceList"))
            {
                presentEvidenceListComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static CharacterPosition ParseCharacterPosition() throws CaseParseException
    {
        String characterPositionString = GetNextToken();
        
        try
        {
            return CharacterPosition.valueOf(characterPositionString);
        }
        catch (IllegalArgumentException ex)
        {
            ThrowForUnknownToken(characterPositionString);
        }
        
        return CharacterPosition.Left;
    }

    private static CharacterDirection ParseCharacterDirection() throws CaseParseException
    {
        String characterDirectionString = GetNextToken();
        
        try
        {
            return CharacterDirection.valueOf(characterDirectionString);
        }
        catch (IllegalArgumentException ex)
        {
            ThrowForUnknownToken(characterDirectionString);
        }
        
        return CharacterDirection.Left;
    }
    
    private static FieldCharacterDirection ParseFieldCharacterDirection() throws CaseParseException
    {
        String fieldCharacterDirectionString = GetNextToken();
        
        try
        {
            return FieldCharacterDirection.valueOf(fieldCharacterDirectionString);
        }
        catch (IllegalArgumentException ex)
        {
            ThrowForUnknownToken(fieldCharacterDirectionString);
        }
        
        return FieldCharacterDirection.Side;
    }

    private static TransitionDirection ParseTransitionDirection() throws CaseParseException
    {
        String transitionDirectionString = GetNextToken();
        
        try
        {
            return TransitionDirection.valueOf(transitionDirectionString);
        }
        catch (IllegalArgumentException ex)
        {
            ThrowForUnknownToken(transitionDirectionString);
        }
        
        return TransitionDirection.West;
    }

    private static Rectangle ParseRectangle()
    {
        return new Rectangle(Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()), Integer.parseInt(GetNextToken()));
    }

    private static Polygon ParsePolygon() throws CaseParseException
    {
        Polygon polygon = new Polygon();
        String[] vertexArray = GetRestOfLine().split(" ");

        if (vertexArray.length % 2 == 1)
        {
            throw new CaseParseException("Number of position parameters for Polygon must be even.");
        }

        for (int i = 0; i < vertexArray.length / 2; i++)
        {
            polygon.addPoint(Integer.parseInt(vertexArray[2 * i]), Integer.parseInt(vertexArray[2 * i + 1]));
        }
        
        return polygon;
    }

    private static void ParseAnimation(String animationId, String endAnimationString) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean animationComplete = false;
        int nextFrameNumber = 1;

        Animation animation = new Animation(animationId);

        EndLine();

        while (!animationComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndAnimation found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Frame"))
            {
                int msDuration = Integer.parseInt(GetNextToken());
                String filePath = GetRestOfLine();
                String frameTextureId = null;//Case.getInstance().getSpriteManager().GetTextureIdFromFilePath(filePath);
            
                //if (frameTextureId == null)
                //{
                    frameTextureId = animationId + "Frame" + nextFrameNumber;
                    nextFrameNumber++;
                    Case.getInstance().getSpriteManager().AddTexture(frameTextureId, filePath);
                //}
            
                animation.AddFrame(msDuration, frameTextureId);
            }
            else if (token.equals("AddHoofstep"))
            {
                EndLine();
                animation.AddHoofstep();
            }
            else
            {
                if (token.equals(endAnimationString))
                {
                    Case.getInstance().getAnimationManager().AddAnimation(animationId, animation);
                    animationComplete = true;
                    EndLine();
                }
                else
                {
                    ThrowForUnknownToken(token);
                }
            }
        }
    }

    private static void ParseVideo(String videoId, String endVideoString) throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean videoComplete = false;

        Video video = new Video(videoId);

        EndLine();

        while (!videoComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndVideo found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Frame"))
            {
                int msDuration = Integer.parseInt(GetNextToken());
                String filePath = GetRestOfLine();
                video.AddFrame(msDuration, filePath);
            }
            else if (token.equals("AddHoofstep"))
            {
                EndLine();
                video.AddHoofstep();
            }
            else if (token.equals("AddSound"))
            {
                String sfxId = GetNextToken();
                EndLine();
                video.AddSpecifiedSound(sfxId);
                
                Case.getInstance().getAudioManager().RequestSfx(sfxId);
            }
            else
            {
                if (token.equals(endVideoString))
                {
                    Case.getInstance().getAnimationManager().AddVideo(videoId, video);
                    videoComplete = true;
                    EndLine();
                }
                else
                {
                    ThrowForUnknownToken(token);
                }
            }
        }
    }

    private static HitBox ParseHitBox() throws CaseParseException
    {
        String token = null;
        boolean hitBoxComplete = false;

        HitBox hitBox = new HitBox();

        EndLine();

        while (!hitBoxComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndHitBox found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Triangle"))
            {
                Vector2 vertex1 = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
                Vector2 vertex2 = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
                Vector2 vertex3 = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
            
                hitBox.AddCollidableObject(CollidableObject.CreateTriangle(vertex1, vertex2, vertex3));
                EndLine();
            }
            else if (token.equals("Rectangle"))
            {
                Vector2 position = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
                Vector2 size = new Vector2(Float.parseFloat(GetNextToken()), Float.parseFloat(GetNextToken()));
            
                hitBox.AddCollidableObject(CollidableObject.CreateRectangle(position, size));
                EndLine();
            }
            else if (token.equals("Polygon"))
            {
                String[] vertexPositionParameters = GetRestOfLine().split(" ");
                
                if (vertexPositionParameters.length % 2 == 1)
                {
                    throw new CaseParseException("Number of position parameters for Polygon must be even.");
                }
                
                ArrayList<Vector2> vertices = new ArrayList<Vector2>();
                
                for (int i = 0; i < vertexPositionParameters.length / 2; i++)
                {
                    vertices.add(new Vector2(Double.parseDouble(vertexPositionParameters[2 * i]), Double.parseDouble(vertexPositionParameters[2 * i + 1])));
                }
            
                hitBox.AddCollidableObject(CollidableObject.CreatePolygon(vertices));
            }
            else if (token.equals("EndHitBox"))
            {
                hitBoxComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }

        return hitBox;
    }

    private static Condition ParseCondition() throws CaseParseException
    {
        String token = null;
        boolean conditionComplete = false;

        Condition condition = new Condition();

        EndLine();

        while (!conditionComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCondition found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("FlagSet"))
            {
                condition.FlagSet(GetNextToken());
                EndLine();
            }
            else if (token.equals("EvidencePresent"))
            {
                condition.EvidencePresent(GetNextToken());
                EndLine();
            }
            else if (token.equals("PartnerPresent"))
            {
                condition.PartnerPresent(GetNextToken());
                EndLine();
            }
            else if (token.equals("ConversationLocked"))
            {
                condition.ConversationLocked(GetNextToken(), GetNextToken());
                EndLine();
            }
            else if (token.equals("TutorialsEnabled"))
            {
                condition.TutorialsEnabled();
                EndLine();
            }
            else if (token.equals("And"))
            {
                condition.And();
                EndLine();
            }
            else if (token.equals("Or"))
            {
                condition.Or();
                EndLine();
            }
            else if (token.equals("Not"))
            {
                condition.Not();
                EndLine();
            }
            else if (token.equals("EndCondition"))
            {
                conditionComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }

        return condition;
    }
    
    private static void ParseCaseMetadata() throws CaseParseException, SlickException, IOException
    {
        String token = null;
        boolean caseMetadataComplete = false;
        String title = null;
        String description = null;
        int majorVersionRequired = -1;
        int minorVersionRequired = -1;
        int revisionVersionRequired = -1;
        BufferedImage imageBeforeCompletion = null;
        BufferedImage imageBeforeCompletionFullSize = null;
        BufferedImage imageAfterCompletion = null;
        BufferedImage imageAfterCompletionFullSize = null;

        EndLine();

        while (!caseMetadataComplete)
        {
            token = GetNextToken();

            if (token == null)
            {
                if (curTokenLine == null)
                {
                    throw new CaseParseException("Unexpected end of file.  No EndCaseMetadata found.");
                }
                else
                {
                    throw new CaseParseException("Unexpected end of line.");
                }
            }

            if (token.equals("Title"))
            {
                title = GetRestOfLine();
            }
            else if (token.equals("Description"))
            {
                description = GetRestOfLine();
            }
            else if (token.equals("RequiredVersion"))
            {
                String requiredVersionString = GetRestOfLine();
                
                Scanner scanner = new Scanner(requiredVersionString.replace('.', ' '));
                majorVersionRequired = scanner.nextInt();
                minorVersionRequired = scanner.nextInt();
                revisionVersionRequired = scanner.nextInt();
            }
            else if (token.equals("ImageBeforeCompletion"))
            {
                String filePath = GetRestOfLine();
                imageBeforeCompletion = ImageIO.read(Paths.get("data", filePath).toFile());
            }
            else if (token.equals("ImageBeforeCompletionFullSize"))
            {
                String filePath = GetRestOfLine();
                imageBeforeCompletionFullSize = ImageIO.read(Paths.get("data", filePath).toFile());
            }
            else if (token.equals("ImageAfterCompletion"))
            {
                String filePath = GetRestOfLine();
                imageAfterCompletion = ImageIO.read(Paths.get("data", filePath).toFile());
            }
            else if (token.equals("ImageAfterCompletionFullSize"))
            {
                String filePath = GetRestOfLine();
                imageAfterCompletionFullSize = ImageIO.read(Paths.get("data", filePath).toFile());
            }
            else if (token.equals("EndCaseMetadata"))
            {
                if (title == null)
                {
                    throw new CaseParseException("Case metadata must contain a title.");
                }
                
                if (description == null)
                {
                    throw new CaseParseException("Case metadata must contain a description.");
                }
                
                if (majorVersionRequired < 0 || minorVersionRequired < 0 || revisionVersionRequired < 0)
                {
                    throw new CaseParseException("Case metadata must contain a required version.");
                }
                
                if (imageBeforeCompletion == null)
                {
                    throw new CaseParseException("Case metadata must contain an image to be displayed before completion.");
                }
                
                if (imageAfterCompletion == null)
                {
                    throw new CaseParseException("Case metadata must contain an image to be displayed after completion.");
                }
                
                Case.getInstance().setMetadata(new CaseMetadata(title, description, majorVersionRequired, minorVersionRequired, revisionVersionRequired, imageBeforeCompletion, imageBeforeCompletionFullSize, imageAfterCompletion, imageAfterCompletionFullSize));
                
                caseMetadataComplete = true;
                EndLine();
            }
            else
            {
                ThrowForUnknownToken(token);
            }
        }
    }

    private static void ThrowForUnknownToken(String token) throws CaseParseException
    {
        throw new CaseParseException("Invalid token '" + token + "'.");
    }

    private static void ParseSourceIntoTokens(String caseSource) throws IOException
    {
        tokenLineList = new ArrayList<TokenLine>();
        String curLine = null;
        int curLineNumber = 1;
        
        BufferedReader tokenReader = new BufferedReader(new StringReader(caseSource));

        while ((curLine = tokenReader.readLine()) != null)
        {
            ArrayList<String> tokenList = new ArrayList<String>(Arrays.asList(curLine.trim().split(" ")));

            for (int i = tokenList.size() - 1; i >= 0; i--)
            {
                if (Utils.isNullOrWhiteSpace(tokenList.get(i)))
                {
                    tokenList.remove(i);
                }
            }

            if (tokenList.size() > 0)
            {
                tokenLineList.add(new TokenLine(tokenList, curLineNumber));
            }

            curLineNumber++;
        }
        
        tokenReader.close();

        if (tokenLineList.size() > 0)
        {
            curTokenLine = tokenLineList.get(0);
        }
    }

    private static String GetNextToken()
    {
        String token = null;

        if (curTokenLine != null && curTokenLine.size() > 0)
        {
            token = curTokenLine.get(0);
            curTokenLine.RemoveAt(0);
        }

        return token;
    }

    private static String GetRestOfLine()
    {
        String restOfLine = "";

        if (curTokenLine != null)
        {
            if (curTokenLine.size() > 0)
            {
                restOfLine = curTokenLine.GetRestOfLine();
            }
            
            tokenLineList.remove(0);

            if (tokenLineList.size() > 0)
            {
                curTokenLine = tokenLineList.get(0);
            }
        }

        return restOfLine;
    }

    private static void EndLine() throws CaseParseException
    {
        if (curTokenLine.size() > 0)
        {
            throw new CaseParseException("Unexpected tokens at end of line.");
        }

        tokenLineList.remove(0);

        if (tokenLineList.size() > 0)
        {
            curTokenLine = tokenLineList.get(0);
        }
    }

    private static class TokenLine
    {
        private ArrayList<String> tokenList;

        public TokenLine(ArrayList<String> tokenList, int lineNumber)
        {
            this.tokenList = tokenList;
            this.lineNumber = lineNumber;
        }

        private int lineNumber;

        public int getLineNumber() { return this.lineNumber; }
        private void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; }

        public int size()
        {
            return this.tokenList.size();
        }

        public String get(int i)
        {
            return this.tokenList.get(i);
        }

        public void RemoveAt(int index)
        {
            this.tokenList.remove(index);
        }

        public String GetRestOfLine()
        {
            String restOfLineString = "";
            
            for (int i = 0; i < this.tokenList.size(); i++)
            {
                if (i > 0)
                {
                    restOfLineString += " ";
                }
                
                restOfLineString += this.tokenList.get(i);
            }
            
            return restOfLineString;
        }
    }
    
    private static class NextNumberParameter
    {
        public int NextForegroundElementNumber;
        public int NextForegroundElementAnimationNumber;
        public int NextZoomedViewBackgroundNumber;
        
        public NextNumberParameter()
        {
            this.NextForegroundElementNumber = 1;
            this.NextForegroundElementAnimationNumber = 1;
            this.NextZoomedViewBackgroundNumber = 1;
        }
    }
    
    private static class BooleanByRefParameter
    {
        public boolean Value;
        
        public BooleanByRefParameter()
        {
            this.Value = false;
        }
    }
}
